x402z-facilitator 0.1.0 → 0.1.11
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/chunk-2BNYP3FU.mjs +768 -0
- package/dist/index.js +19 -2
- package/dist/index.mjs +1 -1
- package/dist/service/bootstrap.js +19 -2
- package/dist/service/bootstrap.mjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,768 @@
|
|
|
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 { registerExactEvmScheme } from "@x402/evm/exact/facilitator";
|
|
12
|
+
import { createWalletClient, http, publicActions } from "viem";
|
|
13
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
14
|
+
|
|
15
|
+
// src/service/service.ts
|
|
16
|
+
import { createServer } from "http";
|
|
17
|
+
async function readJson(req) {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for await (const chunk of req) {
|
|
20
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
21
|
+
}
|
|
22
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
23
|
+
return JSON.parse(body);
|
|
24
|
+
}
|
|
25
|
+
function sendJson(res, status, payload) {
|
|
26
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
27
|
+
res.end(JSON.stringify(payload));
|
|
28
|
+
}
|
|
29
|
+
function createFacilitatorService(config) {
|
|
30
|
+
const server = createServer(async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
const method = req.method ?? "GET";
|
|
33
|
+
const url = req.url ?? "/";
|
|
34
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
35
|
+
console.debug(`[x402z-facilitator] ${method} ${url}`);
|
|
36
|
+
}
|
|
37
|
+
if (!req.url) {
|
|
38
|
+
sendJson(res, 404, { error: "not_found" });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (req.method === "GET" && req.url === "/supported") {
|
|
42
|
+
sendJson(res, 200, config.facilitator.getSupported());
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (req.method === "POST" && req.url === "/verify") {
|
|
46
|
+
const body = await readJson(req);
|
|
47
|
+
const result = await config.facilitator.verify(body.paymentPayload, body.paymentRequirements);
|
|
48
|
+
sendJson(res, 200, result);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (req.method === "POST" && req.url === "/settle") {
|
|
52
|
+
const body = await readJson(req);
|
|
53
|
+
const result = await config.facilitator.settle(body.paymentPayload, body.paymentRequirements);
|
|
54
|
+
sendJson(res, 200, result);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
sendJson(res, 404, { error: "not_found" });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : "internal_error";
|
|
60
|
+
sendJson(res, 500, { error: message });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return server;
|
|
64
|
+
}
|
|
65
|
+
function startFacilitatorService(config) {
|
|
66
|
+
const port = config.port ?? 8040;
|
|
67
|
+
const server = createFacilitatorService(config);
|
|
68
|
+
server.listen(port);
|
|
69
|
+
return { port };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/scheme/scheme.ts
|
|
73
|
+
import { decodeEventLog, getAddress, isAddressEqual } from "viem";
|
|
74
|
+
import {
|
|
75
|
+
confidentialPaymentTypes,
|
|
76
|
+
confidentialTokenAbi,
|
|
77
|
+
hashEncryptedAmountInput
|
|
78
|
+
} from "x402z-shared";
|
|
79
|
+
var batcherAbi = [
|
|
80
|
+
{
|
|
81
|
+
inputs: [
|
|
82
|
+
{ internalType: "address", name: "token", type: "address" },
|
|
83
|
+
{
|
|
84
|
+
components: [
|
|
85
|
+
{
|
|
86
|
+
components: [
|
|
87
|
+
{ internalType: "address", name: "holder", type: "address" },
|
|
88
|
+
{ internalType: "address", name: "payee", type: "address" },
|
|
89
|
+
{ internalType: "uint256", name: "maxClearAmount", type: "uint256" },
|
|
90
|
+
{ internalType: "bytes32", name: "resourceHash", type: "bytes32" },
|
|
91
|
+
{ internalType: "uint48", name: "validAfter", type: "uint48" },
|
|
92
|
+
{ internalType: "uint48", name: "validBefore", type: "uint48" },
|
|
93
|
+
{ internalType: "bytes32", name: "nonce", type: "bytes32" },
|
|
94
|
+
{ internalType: "bytes32", name: "encryptedAmountHash", type: "bytes32" }
|
|
95
|
+
],
|
|
96
|
+
internalType: "struct IFHEToken.ConfidentialPayment",
|
|
97
|
+
name: "p",
|
|
98
|
+
type: "tuple"
|
|
99
|
+
},
|
|
100
|
+
{ internalType: "externalEuint64", name: "encryptedAmountInput", type: "bytes32" },
|
|
101
|
+
{ internalType: "bytes", name: "inputProof", type: "bytes" },
|
|
102
|
+
{ internalType: "bytes", name: "sig", type: "bytes" }
|
|
103
|
+
],
|
|
104
|
+
internalType: "struct FHETokenBatcher.Request[]",
|
|
105
|
+
name: "requests",
|
|
106
|
+
type: "tuple[]"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
name: "batchConfidentialTransferWithAuthorization",
|
|
110
|
+
outputs: [
|
|
111
|
+
{ internalType: "bool[]", name: "successes", type: "bool[]" },
|
|
112
|
+
{ internalType: "bytes32[]", name: "transferredHandles", type: "bytes32[]" }
|
|
113
|
+
],
|
|
114
|
+
stateMutability: "nonpayable",
|
|
115
|
+
type: "function"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
anonymous: false,
|
|
119
|
+
inputs: [
|
|
120
|
+
{ indexed: true, internalType: "uint256", name: "index", type: "uint256" },
|
|
121
|
+
{ indexed: false, internalType: "bytes32", name: "transferredHandle", type: "bytes32" }
|
|
122
|
+
],
|
|
123
|
+
name: "BatchItemSuccess",
|
|
124
|
+
type: "event"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
anonymous: false,
|
|
128
|
+
inputs: [
|
|
129
|
+
{ indexed: true, internalType: "uint256", name: "index", type: "uint256" },
|
|
130
|
+
{ indexed: false, internalType: "bytes", name: "reason", type: "bytes" }
|
|
131
|
+
],
|
|
132
|
+
name: "BatchItemFailure",
|
|
133
|
+
type: "event"
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
var X402zEvmFacilitator = class {
|
|
137
|
+
constructor(config) {
|
|
138
|
+
this.config = config;
|
|
139
|
+
this.scheme = "erc7984-mind-v1";
|
|
140
|
+
this.caipFamily = "eip155:*";
|
|
141
|
+
this.queue = [];
|
|
142
|
+
this.hashFn = config.hashEncryptedAmountInput ?? hashEncryptedAmountInput;
|
|
143
|
+
this.clock = config.clock ?? (() => Math.floor(Date.now() / 1e3));
|
|
144
|
+
this.checkUsedNonces = config.checkUsedNonces ?? true;
|
|
145
|
+
this.waitForReceipt = config.waitForReceipt ?? true;
|
|
146
|
+
this.batchIntervalMs = Math.max(0, config.batchIntervalMs ?? 15e3);
|
|
147
|
+
this.receiptOptions = config.receipt;
|
|
148
|
+
this.batcherAddress = getAddress(config.batcherAddress);
|
|
149
|
+
}
|
|
150
|
+
getExtra(_) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
getSigners(_) {
|
|
154
|
+
return [...this.config.signer.getAddresses()];
|
|
155
|
+
}
|
|
156
|
+
async verify(payload, requirements) {
|
|
157
|
+
const confidentialPayload = payload.payload;
|
|
158
|
+
if (payload.accepted.scheme !== this.scheme || requirements.scheme !== this.scheme) {
|
|
159
|
+
return {
|
|
160
|
+
isValid: false,
|
|
161
|
+
invalidReason: "unsupported_scheme",
|
|
162
|
+
payer: confidentialPayload?.authorization?.holder
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (payload.accepted.network !== requirements.network) {
|
|
166
|
+
return {
|
|
167
|
+
isValid: false,
|
|
168
|
+
invalidReason: "network_mismatch",
|
|
169
|
+
payer: confidentialPayload.authorization.holder
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const extra = requirements.extra;
|
|
173
|
+
const eip712 = extra?.eip712;
|
|
174
|
+
if (!eip712?.name || !eip712?.version) {
|
|
175
|
+
return {
|
|
176
|
+
isValid: false,
|
|
177
|
+
invalidReason: "missing_eip712_domain",
|
|
178
|
+
payer: confidentialPayload.authorization.holder
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const now = this.clock();
|
|
182
|
+
const validAfter = Number(confidentialPayload.authorization.validAfter);
|
|
183
|
+
const validBefore = Number(confidentialPayload.authorization.validBefore);
|
|
184
|
+
if (Number.isNaN(validAfter) || Number.isNaN(validBefore)) {
|
|
185
|
+
return {
|
|
186
|
+
isValid: false,
|
|
187
|
+
invalidReason: "invalid_validity_window",
|
|
188
|
+
payer: confidentialPayload.authorization.holder
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (now < validAfter || now > validBefore) {
|
|
192
|
+
return {
|
|
193
|
+
isValid: false,
|
|
194
|
+
invalidReason: "authorization_expired",
|
|
195
|
+
payer: confidentialPayload.authorization.holder
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (!isAddressEqual(getAddress(confidentialPayload.authorization.payee), getAddress(requirements.payTo))) {
|
|
199
|
+
return {
|
|
200
|
+
isValid: false,
|
|
201
|
+
invalidReason: "recipient_mismatch",
|
|
202
|
+
payer: confidentialPayload.authorization.holder
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const computedHash = this.hashFn(confidentialPayload.encryptedAmountInput);
|
|
206
|
+
if (computedHash !== confidentialPayload.authorization.encryptedAmountHash) {
|
|
207
|
+
return {
|
|
208
|
+
isValid: false,
|
|
209
|
+
invalidReason: "encrypted_amount_mismatch",
|
|
210
|
+
payer: confidentialPayload.authorization.holder
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const chainId = parseInt(requirements.network.split(":")[1]);
|
|
214
|
+
const isValidSignature = await this.config.signer.verifyTypedData({
|
|
215
|
+
address: confidentialPayload.authorization.holder,
|
|
216
|
+
domain: {
|
|
217
|
+
name: eip712.name,
|
|
218
|
+
version: eip712.version,
|
|
219
|
+
chainId,
|
|
220
|
+
verifyingContract: getAddress(requirements.asset)
|
|
221
|
+
},
|
|
222
|
+
types: confidentialPaymentTypes,
|
|
223
|
+
primaryType: "ConfidentialPayment",
|
|
224
|
+
message: {
|
|
225
|
+
holder: getAddress(confidentialPayload.authorization.holder),
|
|
226
|
+
payee: getAddress(confidentialPayload.authorization.payee),
|
|
227
|
+
maxClearAmount: BigInt(confidentialPayload.authorization.maxClearAmount),
|
|
228
|
+
resourceHash: confidentialPayload.authorization.resourceHash,
|
|
229
|
+
validAfter: BigInt(confidentialPayload.authorization.validAfter),
|
|
230
|
+
validBefore: BigInt(confidentialPayload.authorization.validBefore),
|
|
231
|
+
nonce: confidentialPayload.authorization.nonce,
|
|
232
|
+
encryptedAmountHash: confidentialPayload.authorization.encryptedAmountHash
|
|
233
|
+
},
|
|
234
|
+
signature: confidentialPayload.signature
|
|
235
|
+
});
|
|
236
|
+
if (!isValidSignature) {
|
|
237
|
+
return {
|
|
238
|
+
isValid: false,
|
|
239
|
+
invalidReason: "invalid_signature",
|
|
240
|
+
payer: confidentialPayload.authorization.holder
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (this.checkUsedNonces) {
|
|
244
|
+
const used = await this.config.signer.readContract({
|
|
245
|
+
address: getAddress(requirements.asset),
|
|
246
|
+
abi: confidentialTokenAbi,
|
|
247
|
+
functionName: "usedNonces",
|
|
248
|
+
args: [confidentialPayload.authorization.holder, confidentialPayload.authorization.nonce]
|
|
249
|
+
});
|
|
250
|
+
if (used) {
|
|
251
|
+
return {
|
|
252
|
+
isValid: false,
|
|
253
|
+
invalidReason: "nonce_already_used",
|
|
254
|
+
payer: confidentialPayload.authorization.holder
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const txRequest = {
|
|
260
|
+
address: this.batcherAddress,
|
|
261
|
+
abi: batcherAbi,
|
|
262
|
+
functionName: "batchConfidentialTransferWithAuthorization",
|
|
263
|
+
args: [
|
|
264
|
+
getAddress(requirements.asset),
|
|
265
|
+
[
|
|
266
|
+
{
|
|
267
|
+
p: confidentialPayload.authorization,
|
|
268
|
+
encryptedAmountInput: confidentialPayload.encryptedAmountInput,
|
|
269
|
+
inputProof: confidentialPayload.inputProof,
|
|
270
|
+
sig: confidentialPayload.signature
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
]
|
|
274
|
+
};
|
|
275
|
+
const [successes] = await this.config.signer.simulateContract(txRequest);
|
|
276
|
+
if (!successes[0]) {
|
|
277
|
+
return {
|
|
278
|
+
isValid: false,
|
|
279
|
+
invalidReason: "preflight_failed",
|
|
280
|
+
payer: confidentialPayload.authorization.holder
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
return {
|
|
285
|
+
isValid: false,
|
|
286
|
+
invalidReason: "preflight_failed",
|
|
287
|
+
payer: confidentialPayload.authorization.holder
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
isValid: true,
|
|
292
|
+
payer: confidentialPayload.authorization.holder
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async settle(payload, requirements) {
|
|
296
|
+
const valid = await this.verify(payload, requirements);
|
|
297
|
+
const confidentialPayload = payload.payload;
|
|
298
|
+
if (!valid.isValid) {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
errorReason: valid.invalidReason ?? "invalid_payment",
|
|
302
|
+
payer: confidentialPayload.authorization.holder,
|
|
303
|
+
transaction: "",
|
|
304
|
+
network: requirements.network
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
this.queue.push({ payload, requirements, resolve });
|
|
309
|
+
this.scheduleFlush();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
scheduleFlush() {
|
|
313
|
+
if (this.flushTimer) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const delay = this.batchIntervalMs;
|
|
317
|
+
this.flushTimer = setTimeout(() => {
|
|
318
|
+
this.flushTimer = void 0;
|
|
319
|
+
void this.flushQueue();
|
|
320
|
+
}, delay);
|
|
321
|
+
}
|
|
322
|
+
async flushQueue() {
|
|
323
|
+
if (this.queue.length === 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const queued = this.queue;
|
|
327
|
+
this.queue = [];
|
|
328
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
329
|
+
for (const entry of queued) {
|
|
330
|
+
const tokenKey = getAddress(entry.requirements.asset);
|
|
331
|
+
const group = byToken.get(tokenKey) ?? [];
|
|
332
|
+
group.push(entry);
|
|
333
|
+
byToken.set(tokenKey, group);
|
|
334
|
+
}
|
|
335
|
+
for (const [tokenAddress, entries] of byToken.entries()) {
|
|
336
|
+
await this.flushBatch(tokenAddress, entries);
|
|
337
|
+
}
|
|
338
|
+
if (this.queue.length > 0) {
|
|
339
|
+
this.scheduleFlush();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async flushBatch(tokenAddress, entries) {
|
|
343
|
+
const buildRequests = (batchEntries2) => batchEntries2.map(({ entry }) => {
|
|
344
|
+
const confidentialPayload = entry.payload.payload;
|
|
345
|
+
return {
|
|
346
|
+
p: confidentialPayload.authorization,
|
|
347
|
+
encryptedAmountInput: confidentialPayload.encryptedAmountInput,
|
|
348
|
+
inputProof: confidentialPayload.inputProof,
|
|
349
|
+
sig: confidentialPayload.signature
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
const originalEntries = entries.map((entry, index) => ({ entry, index }));
|
|
353
|
+
let batchEntries = originalEntries;
|
|
354
|
+
let requests = buildRequests(batchEntries);
|
|
355
|
+
let txRequest = {
|
|
356
|
+
address: this.batcherAddress,
|
|
357
|
+
abi: batcherAbi,
|
|
358
|
+
functionName: "batchConfidentialTransferWithAuthorization",
|
|
359
|
+
args: [tokenAddress, requests]
|
|
360
|
+
};
|
|
361
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
362
|
+
console.debug("[x402z-facilitator] settle tx", {
|
|
363
|
+
batcherAddress: this.batcherAddress,
|
|
364
|
+
tokenAddress,
|
|
365
|
+
functionName: txRequest.functionName,
|
|
366
|
+
to: txRequest.address,
|
|
367
|
+
size: requests.length
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const [successes, transferredHandles] = await this.config.signer.simulateContract(txRequest);
|
|
372
|
+
if (successes.length === batchEntries.length && transferredHandles.length === batchEntries.length) {
|
|
373
|
+
const filtered = [];
|
|
374
|
+
batchEntries.forEach((item, index) => {
|
|
375
|
+
if (successes[index]) {
|
|
376
|
+
filtered.push(item);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const confidentialPayload = item.entry.payload.payload;
|
|
380
|
+
item.entry.resolve({
|
|
381
|
+
success: false,
|
|
382
|
+
errorReason: "preflight_failed",
|
|
383
|
+
payer: confidentialPayload.authorization.holder,
|
|
384
|
+
transaction: "",
|
|
385
|
+
network: item.entry.requirements.network,
|
|
386
|
+
batch: { index: item.index, success: false, transferredHandle: transferredHandles[index] }
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
if (filtered.length === 0) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
batchEntries = filtered;
|
|
393
|
+
requests = buildRequests(batchEntries);
|
|
394
|
+
txRequest = {
|
|
395
|
+
...txRequest,
|
|
396
|
+
args: [tokenAddress, requests]
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
401
|
+
console.error("[x402z-facilitator] settle tx error", error);
|
|
402
|
+
}
|
|
403
|
+
for (const entry of entries) {
|
|
404
|
+
const confidentialPayload = entry.payload.payload;
|
|
405
|
+
entry.resolve({
|
|
406
|
+
success: false,
|
|
407
|
+
errorReason: "preflight_failed",
|
|
408
|
+
payer: confidentialPayload.authorization.holder,
|
|
409
|
+
transaction: "",
|
|
410
|
+
network: entry.requirements.network
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const settleEntries = batchEntries.map((item) => item.entry);
|
|
416
|
+
let txHash;
|
|
417
|
+
try {
|
|
418
|
+
txHash = await this.config.signer.writeContract(txRequest);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
for (const entry of settleEntries) {
|
|
421
|
+
const confidentialPayload = entry.payload.payload;
|
|
422
|
+
entry.resolve({
|
|
423
|
+
success: false,
|
|
424
|
+
errorReason: "settlement_failed",
|
|
425
|
+
payer: confidentialPayload.authorization.holder,
|
|
426
|
+
transaction: "",
|
|
427
|
+
network: entry.requirements.network
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
433
|
+
console.debug("[x402z-facilitator] tx submitted", txHash);
|
|
434
|
+
}
|
|
435
|
+
let receipt;
|
|
436
|
+
if (this.waitForReceipt) {
|
|
437
|
+
receipt = await this.config.signer.waitForTransactionReceipt({ hash: txHash });
|
|
438
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
439
|
+
console.debug("[x402z-facilitator] tx receipt", receipt);
|
|
440
|
+
}
|
|
441
|
+
if (receipt.status !== "success") {
|
|
442
|
+
for (const entry of settleEntries) {
|
|
443
|
+
const confidentialPayload = entry.payload.payload;
|
|
444
|
+
entry.resolve({
|
|
445
|
+
success: false,
|
|
446
|
+
errorReason: "settlement_failed",
|
|
447
|
+
payer: confidentialPayload.authorization.holder,
|
|
448
|
+
transaction: txHash,
|
|
449
|
+
network: entry.requirements.network
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (!this.waitForReceipt) {
|
|
456
|
+
settleEntries.forEach((entry) => {
|
|
457
|
+
const confidentialPayload = entry.payload.payload;
|
|
458
|
+
entry.resolve({
|
|
459
|
+
success: true,
|
|
460
|
+
payer: confidentialPayload.authorization.holder,
|
|
461
|
+
transaction: txHash,
|
|
462
|
+
network: entry.requirements.network
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const batchResults = /* @__PURE__ */ new Map();
|
|
468
|
+
if (receipt?.logs) {
|
|
469
|
+
for (const log of receipt.logs) {
|
|
470
|
+
if (!log?.address) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (!isAddressEqual(getAddress(log.address), this.batcherAddress)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const decoded = decodeEventLog({
|
|
477
|
+
abi: batcherAbi,
|
|
478
|
+
data: log.data,
|
|
479
|
+
topics: log.topics
|
|
480
|
+
});
|
|
481
|
+
if (process.env.X402Z_DEBUG === "1") {
|
|
482
|
+
console.debug("[x402z-facilitator] batch log", decoded);
|
|
483
|
+
}
|
|
484
|
+
if (decoded.eventName === "BatchItemSuccess") {
|
|
485
|
+
const args = decoded.args;
|
|
486
|
+
batchResults.set(Number(args.index), {
|
|
487
|
+
success: true,
|
|
488
|
+
transferredHandle: args.transferredHandle
|
|
489
|
+
});
|
|
490
|
+
} else if (decoded.eventName === "BatchItemFailure") {
|
|
491
|
+
const args = decoded.args;
|
|
492
|
+
batchResults.set(Number(args.index), {
|
|
493
|
+
success: false,
|
|
494
|
+
failureReason: args.reason
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
settleEntries.forEach((entry, index) => {
|
|
500
|
+
const confidentialPayload = entry.payload.payload;
|
|
501
|
+
const batchResult = batchResults.get(index);
|
|
502
|
+
if (!batchResult || !batchResult.success) {
|
|
503
|
+
entry.resolve({
|
|
504
|
+
success: false,
|
|
505
|
+
errorReason: "settlement_failed",
|
|
506
|
+
payer: confidentialPayload.authorization.holder,
|
|
507
|
+
transaction: txHash,
|
|
508
|
+
network: entry.requirements.network,
|
|
509
|
+
...batchResult ? { batch: { index, ...batchResult } } : {}
|
|
510
|
+
});
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
entry.resolve({
|
|
514
|
+
success: true,
|
|
515
|
+
payer: confidentialPayload.authorization.holder,
|
|
516
|
+
transaction: txHash,
|
|
517
|
+
network: entry.requirements.network,
|
|
518
|
+
batch: { index, ...batchResult }
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/service/bootstrap.ts
|
|
525
|
+
function requireEnv(key) {
|
|
526
|
+
const value = process.env[key];
|
|
527
|
+
if (!value) {
|
|
528
|
+
throw new Error(`Missing required env var: ${key}`);
|
|
529
|
+
}
|
|
530
|
+
return value;
|
|
531
|
+
}
|
|
532
|
+
function createFacilitatorFromEnv() {
|
|
533
|
+
const privateKey = requireEnv("FACILITATOR_EVM_PRIVATE_KEY");
|
|
534
|
+
const chainId = Number(process.env.FACILITATOR_EVM_CHAIN_ID ?? "11155111");
|
|
535
|
+
const rpcUrl = process.env.FACILITATOR_EVM_RPC_URL ?? process.env.RPC_URL;
|
|
536
|
+
if (!rpcUrl) {
|
|
537
|
+
throw new Error("Missing required env var: RPC_URL or FACILITATOR_EVM_RPC_URL");
|
|
538
|
+
}
|
|
539
|
+
const networks = (process.env.FACILITATOR_NETWORKS ?? "eip155:11155111").split(",").map((network) => network.trim()).filter(Boolean);
|
|
540
|
+
const waitForReceipt = (process.env.FACILITATOR_WAIT_FOR_RECEIPT ?? "true") === "true";
|
|
541
|
+
const batcherAddress = process.env.FACILITATOR_BATCHER_ADDRESS;
|
|
542
|
+
if (!batcherAddress) {
|
|
543
|
+
throw new Error("FACILITATOR_BATCHER_ADDRESS is required");
|
|
544
|
+
}
|
|
545
|
+
const confidentialAsset = process.env.FACILITATOR_CONFIDENTIAL_ASSET;
|
|
546
|
+
const confidentialEip712Name = process.env.FACILITATOR_CONFIDENTIAL_EIP712_NAME;
|
|
547
|
+
const confidentialEip712Version = process.env.FACILITATOR_CONFIDENTIAL_EIP712_VERSION;
|
|
548
|
+
const confidentialDecimals = process.env.FACILITATOR_CONFIDENTIAL_DECIMALS ? Number(process.env.FACILITATOR_CONFIDENTIAL_DECIMALS) : void 0;
|
|
549
|
+
const exactAsset = process.env.FACILITATOR_EXACT_ASSET;
|
|
550
|
+
const exactDecimals = process.env.FACILITATOR_EXACT_DECIMALS ? Number(process.env.FACILITATOR_EXACT_DECIMALS) : void 0;
|
|
551
|
+
const exactNetwork = process.env.FACILITATOR_EXACT_NETWORK;
|
|
552
|
+
const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
|
|
553
|
+
const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
|
|
554
|
+
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
555
|
+
const receiptPollingIntervalMs = process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS ? Number(process.env.FACILITATOR_RECEIPT_POLLING_INTERVAL_MS) : void 0;
|
|
556
|
+
const gasMultiplier = process.env.FACILITATOR_GAS_MULTIPLIER ? Number(process.env.FACILITATOR_GAS_MULTIPLIER) : void 0;
|
|
557
|
+
const feeMultiplier = process.env.FACILITATOR_FEE_MULTIPLIER ? Number(process.env.FACILITATOR_FEE_MULTIPLIER) : void 0;
|
|
558
|
+
const debugEnabled = process.env.X402Z_DEBUG === "1";
|
|
559
|
+
const account = privateKeyToAccount(privateKey);
|
|
560
|
+
if (debugEnabled) {
|
|
561
|
+
console.debug("[x402z-facilitator] config", {
|
|
562
|
+
chainId,
|
|
563
|
+
networks,
|
|
564
|
+
rpcUrl,
|
|
565
|
+
waitForReceipt,
|
|
566
|
+
batcherAddress,
|
|
567
|
+
gasMultiplier,
|
|
568
|
+
feeMultiplier,
|
|
569
|
+
batchIntervalMs,
|
|
570
|
+
receipt: {
|
|
571
|
+
confirmations: receiptConfirmations,
|
|
572
|
+
timeoutMs: receiptTimeoutMs,
|
|
573
|
+
pollingIntervalMs: receiptPollingIntervalMs
|
|
574
|
+
},
|
|
575
|
+
address: account.address
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
const client = createWalletClient({
|
|
579
|
+
account,
|
|
580
|
+
chain: {
|
|
581
|
+
id: chainId,
|
|
582
|
+
name: "custom",
|
|
583
|
+
nativeCurrency: { name: "native", symbol: "NATIVE", decimals: 18 },
|
|
584
|
+
rpcUrls: { default: { http: [rpcUrl] } }
|
|
585
|
+
},
|
|
586
|
+
transport: http(rpcUrl)
|
|
587
|
+
}).extend(publicActions);
|
|
588
|
+
const baseSigner = toFacilitatorEvmSigner({
|
|
589
|
+
address: account.address,
|
|
590
|
+
readContract: (args) => client.readContract({
|
|
591
|
+
...args,
|
|
592
|
+
args: args.args || []
|
|
593
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
594
|
+
}),
|
|
595
|
+
verifyTypedData: (args) => client.verifyTypedData(args),
|
|
596
|
+
writeContract: async (args) => {
|
|
597
|
+
let gas;
|
|
598
|
+
let maxFeePerGas;
|
|
599
|
+
let maxPriorityFeePerGas;
|
|
600
|
+
if (gasMultiplier && gasMultiplier > 0) {
|
|
601
|
+
try {
|
|
602
|
+
const estimated = await client.estimateContractGas({
|
|
603
|
+
...args,
|
|
604
|
+
args: args.args || [],
|
|
605
|
+
account
|
|
606
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
607
|
+
});
|
|
608
|
+
const scale = BigInt(Math.round(gasMultiplier * 1e3));
|
|
609
|
+
const scaled = estimated * scale / 1000n;
|
|
610
|
+
gas = scaled > estimated ? scaled : estimated;
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (debugEnabled) {
|
|
613
|
+
console.debug("[x402z-facilitator] gas estimate failed", error);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
618
|
+
try {
|
|
619
|
+
const fees = await client.estimateFeesPerGas();
|
|
620
|
+
if (fees.maxFeePerGas) {
|
|
621
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
622
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
623
|
+
}
|
|
624
|
+
if (fees.maxPriorityFeePerGas) {
|
|
625
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
626
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
627
|
+
}
|
|
628
|
+
} catch (error) {
|
|
629
|
+
if (debugEnabled) {
|
|
630
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return client.writeContract({
|
|
635
|
+
...args,
|
|
636
|
+
args: args.args || [],
|
|
637
|
+
...gas ? { gas } : {},
|
|
638
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
639
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
640
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
sendTransaction: async (args) => {
|
|
644
|
+
let maxFeePerGas;
|
|
645
|
+
let maxPriorityFeePerGas;
|
|
646
|
+
if (feeMultiplier && feeMultiplier > 0) {
|
|
647
|
+
try {
|
|
648
|
+
const fees = await client.estimateFeesPerGas();
|
|
649
|
+
if (fees.maxFeePerGas) {
|
|
650
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
651
|
+
maxFeePerGas = fees.maxFeePerGas * scale / 1000n;
|
|
652
|
+
}
|
|
653
|
+
if (fees.maxPriorityFeePerGas) {
|
|
654
|
+
const scale = BigInt(Math.round(feeMultiplier * 1e3));
|
|
655
|
+
maxPriorityFeePerGas = fees.maxPriorityFeePerGas * scale / 1000n;
|
|
656
|
+
}
|
|
657
|
+
} catch (error) {
|
|
658
|
+
if (debugEnabled) {
|
|
659
|
+
console.debug("[x402z-facilitator] fee estimate failed", error);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return client.sendTransaction({
|
|
664
|
+
to: args.to,
|
|
665
|
+
data: args.data,
|
|
666
|
+
...maxFeePerGas ? { maxFeePerGas } : {},
|
|
667
|
+
...maxPriorityFeePerGas ? { maxPriorityFeePerGas } : {}
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
waitForTransactionReceipt: (args) => client.waitForTransactionReceipt(args),
|
|
671
|
+
getCode: (args) => client.getCode(args)
|
|
672
|
+
});
|
|
673
|
+
const signer = {
|
|
674
|
+
...baseSigner,
|
|
675
|
+
simulateContract: async (args) => {
|
|
676
|
+
const result = await client.simulateContract({
|
|
677
|
+
...args,
|
|
678
|
+
args: args.args || [],
|
|
679
|
+
account
|
|
680
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
681
|
+
});
|
|
682
|
+
return result.result;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
const facilitator = new x402Facilitator();
|
|
686
|
+
registerExactEvmScheme(facilitator, {
|
|
687
|
+
signer: baseSigner,
|
|
688
|
+
networks
|
|
689
|
+
});
|
|
690
|
+
for (const network of networks) {
|
|
691
|
+
facilitator.register(
|
|
692
|
+
network,
|
|
693
|
+
new X402zEvmFacilitator({
|
|
694
|
+
signer,
|
|
695
|
+
waitForReceipt,
|
|
696
|
+
batcherAddress,
|
|
697
|
+
batchIntervalMs,
|
|
698
|
+
receipt: {
|
|
699
|
+
confirmations: receiptConfirmations,
|
|
700
|
+
timeoutMs: receiptTimeoutMs,
|
|
701
|
+
pollingIntervalMs: receiptPollingIntervalMs
|
|
702
|
+
}
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const supportedConfidential = confidentialAsset && confidentialEip712Name && confidentialEip712Version && batcherAddress ? {
|
|
707
|
+
asset: confidentialAsset,
|
|
708
|
+
eip712: {
|
|
709
|
+
name: confidentialEip712Name,
|
|
710
|
+
version: confidentialEip712Version
|
|
711
|
+
},
|
|
712
|
+
decimals: confidentialDecimals ?? 6,
|
|
713
|
+
batcherAddress
|
|
714
|
+
} : null;
|
|
715
|
+
const supportedExact = exactAsset ? {
|
|
716
|
+
asset: exactAsset,
|
|
717
|
+
decimals: exactDecimals ?? 6
|
|
718
|
+
} : null;
|
|
719
|
+
if (supportedConfidential || supportedExact) {
|
|
720
|
+
const originalGetSupported = facilitator.getSupported.bind(facilitator);
|
|
721
|
+
facilitator.getSupported = () => {
|
|
722
|
+
const supported = originalGetSupported();
|
|
723
|
+
return {
|
|
724
|
+
...supported,
|
|
725
|
+
kinds: supported.kinds.map((kind) => {
|
|
726
|
+
if (kind.scheme !== "erc7984-mind-v1") {
|
|
727
|
+
if (!supportedExact || kind.scheme !== "exact") {
|
|
728
|
+
return kind;
|
|
729
|
+
}
|
|
730
|
+
if (exactNetwork && kind.network !== exactNetwork) {
|
|
731
|
+
return kind;
|
|
732
|
+
}
|
|
733
|
+
const extra2 = {
|
|
734
|
+
...kind.extra,
|
|
735
|
+
exact: supportedExact
|
|
736
|
+
};
|
|
737
|
+
return { ...kind, extra: extra2 };
|
|
738
|
+
}
|
|
739
|
+
const extra = {
|
|
740
|
+
...kind.extra,
|
|
741
|
+
confidential: supportedConfidential
|
|
742
|
+
};
|
|
743
|
+
return { ...kind, extra };
|
|
744
|
+
})
|
|
745
|
+
};
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return facilitator;
|
|
749
|
+
}
|
|
750
|
+
function startFacilitator() {
|
|
751
|
+
const facilitator = createFacilitatorFromEnv();
|
|
752
|
+
const port = Number(process.env.FACILITATOR_PORT ?? "8040");
|
|
753
|
+
const server = createFacilitatorService({ facilitator, port });
|
|
754
|
+
server.listen(port);
|
|
755
|
+
return { port };
|
|
756
|
+
}
|
|
757
|
+
if (__require.main === module) {
|
|
758
|
+
const { port } = startFacilitator();
|
|
759
|
+
console.log(`Confidential facilitator listening on :${port}`);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export {
|
|
763
|
+
X402zEvmFacilitator,
|
|
764
|
+
createFacilitatorService,
|
|
765
|
+
startFacilitatorService,
|
|
766
|
+
createFacilitatorFromEnv,
|
|
767
|
+
startFacilitator
|
|
768
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -573,6 +573,9 @@ function createFacilitatorFromEnv() {
|
|
|
573
573
|
const confidentialEip712Name = process.env.FACILITATOR_CONFIDENTIAL_EIP712_NAME;
|
|
574
574
|
const confidentialEip712Version = process.env.FACILITATOR_CONFIDENTIAL_EIP712_VERSION;
|
|
575
575
|
const confidentialDecimals = process.env.FACILITATOR_CONFIDENTIAL_DECIMALS ? Number(process.env.FACILITATOR_CONFIDENTIAL_DECIMALS) : void 0;
|
|
576
|
+
const exactAsset = process.env.FACILITATOR_EXACT_ASSET;
|
|
577
|
+
const exactDecimals = process.env.FACILITATOR_EXACT_DECIMALS ? Number(process.env.FACILITATOR_EXACT_DECIMALS) : void 0;
|
|
578
|
+
const exactNetwork = process.env.FACILITATOR_EXACT_NETWORK;
|
|
576
579
|
const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
|
|
577
580
|
const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
|
|
578
581
|
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
@@ -736,7 +739,11 @@ function createFacilitatorFromEnv() {
|
|
|
736
739
|
decimals: confidentialDecimals ?? 6,
|
|
737
740
|
batcherAddress
|
|
738
741
|
} : null;
|
|
739
|
-
|
|
742
|
+
const supportedExact = exactAsset ? {
|
|
743
|
+
asset: exactAsset,
|
|
744
|
+
decimals: exactDecimals ?? 6
|
|
745
|
+
} : null;
|
|
746
|
+
if (supportedConfidential || supportedExact) {
|
|
740
747
|
const originalGetSupported = facilitator.getSupported.bind(facilitator);
|
|
741
748
|
facilitator.getSupported = () => {
|
|
742
749
|
const supported = originalGetSupported();
|
|
@@ -744,7 +751,17 @@ function createFacilitatorFromEnv() {
|
|
|
744
751
|
...supported,
|
|
745
752
|
kinds: supported.kinds.map((kind) => {
|
|
746
753
|
if (kind.scheme !== "erc7984-mind-v1") {
|
|
747
|
-
|
|
754
|
+
if (!supportedExact || kind.scheme !== "exact") {
|
|
755
|
+
return kind;
|
|
756
|
+
}
|
|
757
|
+
if (exactNetwork && kind.network !== exactNetwork) {
|
|
758
|
+
return kind;
|
|
759
|
+
}
|
|
760
|
+
const extra2 = {
|
|
761
|
+
...kind.extra,
|
|
762
|
+
exact: supportedExact
|
|
763
|
+
};
|
|
764
|
+
return { ...kind, extra: extra2 };
|
|
748
765
|
}
|
|
749
766
|
const extra = {
|
|
750
767
|
...kind.extra,
|
package/dist/index.mjs
CHANGED
|
@@ -554,6 +554,9 @@ function createFacilitatorFromEnv() {
|
|
|
554
554
|
const confidentialEip712Name = process.env.FACILITATOR_CONFIDENTIAL_EIP712_NAME;
|
|
555
555
|
const confidentialEip712Version = process.env.FACILITATOR_CONFIDENTIAL_EIP712_VERSION;
|
|
556
556
|
const confidentialDecimals = process.env.FACILITATOR_CONFIDENTIAL_DECIMALS ? Number(process.env.FACILITATOR_CONFIDENTIAL_DECIMALS) : void 0;
|
|
557
|
+
const exactAsset = process.env.FACILITATOR_EXACT_ASSET;
|
|
558
|
+
const exactDecimals = process.env.FACILITATOR_EXACT_DECIMALS ? Number(process.env.FACILITATOR_EXACT_DECIMALS) : void 0;
|
|
559
|
+
const exactNetwork = process.env.FACILITATOR_EXACT_NETWORK;
|
|
557
560
|
const batchIntervalMs = process.env.FACILITATOR_BATCH_INTERVAL_MS ? Number(process.env.FACILITATOR_BATCH_INTERVAL_MS) : void 0;
|
|
558
561
|
const receiptTimeoutMs = process.env.FACILITATOR_RECEIPT_TIMEOUT_MS ? Number(process.env.FACILITATOR_RECEIPT_TIMEOUT_MS) : void 0;
|
|
559
562
|
const receiptConfirmations = process.env.FACILITATOR_RECEIPT_CONFIRMATIONS ? Number(process.env.FACILITATOR_RECEIPT_CONFIRMATIONS) : void 0;
|
|
@@ -717,7 +720,11 @@ function createFacilitatorFromEnv() {
|
|
|
717
720
|
decimals: confidentialDecimals ?? 6,
|
|
718
721
|
batcherAddress
|
|
719
722
|
} : null;
|
|
720
|
-
|
|
723
|
+
const supportedExact = exactAsset ? {
|
|
724
|
+
asset: exactAsset,
|
|
725
|
+
decimals: exactDecimals ?? 6
|
|
726
|
+
} : null;
|
|
727
|
+
if (supportedConfidential || supportedExact) {
|
|
721
728
|
const originalGetSupported = facilitator.getSupported.bind(facilitator);
|
|
722
729
|
facilitator.getSupported = () => {
|
|
723
730
|
const supported = originalGetSupported();
|
|
@@ -725,7 +732,17 @@ function createFacilitatorFromEnv() {
|
|
|
725
732
|
...supported,
|
|
726
733
|
kinds: supported.kinds.map((kind) => {
|
|
727
734
|
if (kind.scheme !== "erc7984-mind-v1") {
|
|
728
|
-
|
|
735
|
+
if (!supportedExact || kind.scheme !== "exact") {
|
|
736
|
+
return kind;
|
|
737
|
+
}
|
|
738
|
+
if (exactNetwork && kind.network !== exactNetwork) {
|
|
739
|
+
return kind;
|
|
740
|
+
}
|
|
741
|
+
const extra2 = {
|
|
742
|
+
...kind.extra,
|
|
743
|
+
exact: supportedExact
|
|
744
|
+
};
|
|
745
|
+
return { ...kind, extra: extra2 };
|
|
729
746
|
}
|
|
730
747
|
const extra = {
|
|
731
748
|
...kind.extra,
|