@x402x/facilitator-sdk 2.0.0

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/index.mjs ADDED
@@ -0,0 +1,948 @@
1
+ import { FacilitatorValidationError, toCanonicalNetworkKey, getNetworkName, getNetworkConfig, SETTLEMENT_ROUTER_ABI, isSettlementMode, parseSettlementExtra, SettlementRouterError, calculateCommitment } from '@x402x/extensions';
2
+ export { FacilitatorValidationError, SETTLEMENT_ROUTER_ABI, SettlementRouterError, getNetworkConfig, isSettlementMode, parseSettlementExtra } from '@x402x/extensions';
3
+ import { createPublicClient, http, createWalletClient, parseErc6492Signature, verifyTypedData } from 'viem';
4
+ import { privateKeyToAccount } from 'viem/accounts';
5
+
6
+ // src/facilitator.ts
7
+ function isValidEthereumAddress(address) {
8
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
9
+ }
10
+ function isValidHex(hex) {
11
+ return /^0x[a-fA-F0-9]*$/.test(hex) && hex.length % 2 === 0 && hex.length >= 2;
12
+ }
13
+ function isValid32ByteHex(hex) {
14
+ return /^0x[a-fA-F0-9]{64}$/.test(hex);
15
+ }
16
+ function isValid256BitHex(hex) {
17
+ return /^0x[a-fA-F0-9]{1,64}$/.test(hex);
18
+ }
19
+ function validateSettlementRouter(network, router, allowedRouters, networkConfig) {
20
+ if (!isValidEthereumAddress(router)) {
21
+ throw new FacilitatorValidationError(`Invalid SettlementRouter address: ${router}`);
22
+ }
23
+ if (allowedRouters && network in allowedRouters) {
24
+ const networkAllowedRouters = allowedRouters[network];
25
+ if (networkAllowedRouters.length > 0 && !networkAllowedRouters.includes(router)) {
26
+ throw new FacilitatorValidationError(
27
+ `SettlementRouter ${router} not allowed for network ${network}. Allowed routers: ${networkAllowedRouters.join(", ")}`
28
+ );
29
+ }
30
+ }
31
+ if (networkConfig?.settlementRouter && router !== networkConfig.settlementRouter) {
32
+ throw new FacilitatorValidationError(
33
+ `SettlementRouter ${router} does not match network config expected router ${networkConfig.settlementRouter}`
34
+ );
35
+ }
36
+ return router;
37
+ }
38
+ function validateSettlementExtra(extra) {
39
+ if (!extra || typeof extra !== "object") {
40
+ throw new FacilitatorValidationError("Missing or invalid extra field");
41
+ }
42
+ const e = extra;
43
+ if (!e.settlementRouter || typeof e.settlementRouter !== "string") {
44
+ throw new FacilitatorValidationError("Missing or invalid settlementRouter");
45
+ }
46
+ if (!isValidEthereumAddress(e.settlementRouter)) {
47
+ throw new FacilitatorValidationError("Invalid settlementRouter address format");
48
+ }
49
+ if (!e.salt || typeof e.salt !== "string") {
50
+ throw new FacilitatorValidationError("Missing or invalid salt");
51
+ }
52
+ if (!isValid32ByteHex(e.salt)) {
53
+ throw new FacilitatorValidationError("Salt must be a 32-byte hex string");
54
+ }
55
+ if (!e.payTo || typeof e.payTo !== "string") {
56
+ throw new FacilitatorValidationError("Missing or invalid payTo");
57
+ }
58
+ if (!isValidEthereumAddress(e.payTo)) {
59
+ throw new FacilitatorValidationError("Invalid payTo address format");
60
+ }
61
+ if (!e.facilitatorFee || typeof e.facilitatorFee !== "string") {
62
+ throw new FacilitatorValidationError("Missing or invalid facilitatorFee");
63
+ }
64
+ if (!isValid256BitHex(e.facilitatorFee)) {
65
+ throw new FacilitatorValidationError("Facilitator fee must be a valid hex number");
66
+ }
67
+ if (!e.hook || typeof e.hook !== "string") {
68
+ throw new FacilitatorValidationError("Missing or invalid hook");
69
+ }
70
+ if (!isValidEthereumAddress(e.hook)) {
71
+ throw new FacilitatorValidationError("Invalid hook address format");
72
+ }
73
+ if (!e.hookData || typeof e.hookData !== "string") {
74
+ throw new FacilitatorValidationError("Missing or invalid hookData");
75
+ }
76
+ if (!isValidHex(e.hookData)) {
77
+ throw new FacilitatorValidationError("Hook data must be valid hex");
78
+ }
79
+ return {
80
+ settlementRouter: e.settlementRouter,
81
+ salt: e.salt,
82
+ payTo: e.payTo,
83
+ facilitatorFee: e.facilitatorFee,
84
+ hook: e.hook,
85
+ hookData: e.hookData
86
+ };
87
+ }
88
+ function validateNetwork(network) {
89
+ if (!network || typeof network !== "string") {
90
+ throw new FacilitatorValidationError("Invalid network: must be a non-empty string");
91
+ }
92
+ if (!/^(eip155:\d+|[a-z][a-z0-9-]*[a-z0-9])$/.test(network)) {
93
+ throw new FacilitatorValidationError(`Invalid network format: ${network}`);
94
+ }
95
+ return network;
96
+ }
97
+ function validateFacilitatorConfig(config) {
98
+ if (!config.signer && !config.privateKey) {
99
+ throw new FacilitatorValidationError(
100
+ "Missing signer or privateKey in facilitator configuration"
101
+ );
102
+ }
103
+ if (config.signer) {
104
+ if (!isValidEthereumAddress(config.signer)) {
105
+ throw new FacilitatorValidationError(`Invalid signer address: ${config.signer}`);
106
+ }
107
+ }
108
+ if (config.privateKey) {
109
+ const privateKey = config.privateKey;
110
+ const hasPrefix = privateKey.startsWith("0x") || privateKey.startsWith("0X");
111
+ const hexBody = hasPrefix ? privateKey.slice(2) : privateKey;
112
+ if (!/^[a-fA-F0-9]{64}$/.test(hexBody)) {
113
+ throw new FacilitatorValidationError(
114
+ "Invalid private key format: must be 32-byte hex string (64 hex chars, with optional 0x prefix)"
115
+ );
116
+ }
117
+ }
118
+ if (config.allowedRouters) {
119
+ for (const [network, routers] of Object.entries(config.allowedRouters)) {
120
+ validateNetwork(network);
121
+ if (!Array.isArray(routers)) {
122
+ throw new FacilitatorValidationError(`Allowed routers for ${network} must be an array`);
123
+ }
124
+ for (const router of routers) {
125
+ if (!isValidEthereumAddress(router)) {
126
+ throw new FacilitatorValidationError(`Invalid router address for ${network}: ${router}`);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ if (config.rpcUrls) {
132
+ for (const [network, rpcUrl] of Object.entries(config.rpcUrls)) {
133
+ validateNetwork(network);
134
+ if (typeof rpcUrl !== "string" || !rpcUrl.startsWith("http")) {
135
+ throw new FacilitatorValidationError(`Invalid RPC URL for ${network}: ${rpcUrl}`);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ function validateGasLimit(gasLimit) {
141
+ if (gasLimit <= 0n) {
142
+ throw new FacilitatorValidationError("Gas limit must be positive");
143
+ }
144
+ if (gasLimit > 10000000n) {
145
+ throw new FacilitatorValidationError("Gas limit too large (> 10M)");
146
+ }
147
+ }
148
+ function validateGasMultiplier(multiplier) {
149
+ if (multiplier <= 0) {
150
+ throw new FacilitatorValidationError("Gas multiplier must be positive");
151
+ }
152
+ if (multiplier > 5) {
153
+ throw new FacilitatorValidationError("Gas multiplier too large (> 5x)");
154
+ }
155
+ }
156
+ function validateFeeAmount(fee, minFee, maxFee) {
157
+ let feeBigInt;
158
+ try {
159
+ feeBigInt = BigInt(fee);
160
+ } catch (error) {
161
+ throw new FacilitatorValidationError(`Invalid fee amount: ${fee}. Must be a valid number.`);
162
+ }
163
+ if (feeBigInt < 0n) {
164
+ throw new FacilitatorValidationError("Fee cannot be negative");
165
+ }
166
+ if (minFee && feeBigInt < BigInt(minFee)) {
167
+ throw new FacilitatorValidationError(`Fee below minimum: ${fee} < ${minFee}`);
168
+ }
169
+ if (maxFee && feeBigInt > BigInt(maxFee)) {
170
+ throw new FacilitatorValidationError(`Fee above maximum: ${fee} > ${maxFee}`);
171
+ }
172
+ }
173
+ function networkConfigToChain(networkConfig, rpcUrl) {
174
+ return {
175
+ id: networkConfig.chainId,
176
+ name: networkConfig.name,
177
+ nativeCurrency: {
178
+ name: networkConfig.metadata?.nativeToken || "ETH",
179
+ symbol: networkConfig.metadata?.nativeToken || "ETH",
180
+ decimals: 18
181
+ },
182
+ rpcUrls: {
183
+ default: {
184
+ http: [rpcUrl]
185
+ }
186
+ },
187
+ blockExplorers: {
188
+ default: {
189
+ name: "Explorer",
190
+ url: (() => {
191
+ const addressSuffix = "/address/";
192
+ const baseUrl = networkConfig.addressExplorerBaseUrl;
193
+ return baseUrl.endsWith(addressSuffix) ? baseUrl.slice(0, -addressSuffix.length) : baseUrl;
194
+ })()
195
+ }
196
+ },
197
+ testnet: networkConfig.type === "testnet"
198
+ };
199
+ }
200
+ function createPublicClientForNetwork(network, rpcUrls) {
201
+ const canonicalNetwork = toCanonicalNetworkKey(network);
202
+ const v1NetworkName = getNetworkName(canonicalNetwork);
203
+ const networkConfig = getNetworkConfig(v1NetworkName);
204
+ if (!networkConfig) {
205
+ throw new Error(`Network configuration not found for network: ${network}`);
206
+ }
207
+ const rpcUrl = rpcUrls?.[network] || rpcUrls?.[v1NetworkName] || rpcUrls?.[canonicalNetwork];
208
+ if (!rpcUrl) {
209
+ throw new Error(`No RPC URL available for network: ${network}. Please provide RPC URL in config.`);
210
+ }
211
+ const chain = networkConfigToChain(networkConfig, rpcUrl);
212
+ return createPublicClient({
213
+ chain,
214
+ transport: http(rpcUrl)
215
+ });
216
+ }
217
+ function createWalletClientForNetwork(network, signer, rpcUrls, transport, privateKey) {
218
+ const canonicalNetwork = toCanonicalNetworkKey(network);
219
+ const v1NetworkName = getNetworkName(canonicalNetwork);
220
+ const networkConfig = getNetworkConfig(v1NetworkName);
221
+ const rpcUrl = rpcUrls?.[network] || rpcUrls?.[v1NetworkName] || rpcUrls?.[canonicalNetwork];
222
+ if (!rpcUrl) {
223
+ throw new Error(`No RPC URL available for network: ${network}. Please provide RPC URL in config.`);
224
+ }
225
+ if (!signer && !privateKey) {
226
+ throw new Error("Either signer or privateKey must be provided to create wallet client");
227
+ }
228
+ let account;
229
+ if (privateKey) {
230
+ account = privateKeyToAccount(privateKey);
231
+ } else if (signer) {
232
+ account = signer;
233
+ } else {
234
+ throw new Error("Failed to create account: neither signer nor privateKey provided");
235
+ }
236
+ const chain = networkConfigToChain(networkConfig, rpcUrl);
237
+ return createWalletClient({
238
+ account,
239
+ chain,
240
+ transport: transport || http(rpcUrl)
241
+ });
242
+ }
243
+ function calculateGasLimit(baseFee, facilitatorFee, gasMultiplier = 1.2) {
244
+ validateGasMultiplier(gasMultiplier);
245
+ const baseGas = 200000n;
246
+ const hookGas = facilitatorFee !== "0x0" ? 100000n : 0n;
247
+ const totalGas = (baseGas + hookGas) * BigInt(Math.ceil(gasMultiplier * 100)) / 100n;
248
+ validateGasLimit(totalGas);
249
+ return totalGas;
250
+ }
251
+ async function checkIfSettled(publicClient, router, contextKey) {
252
+ try {
253
+ const isSettled = await publicClient.readContract({
254
+ address: router,
255
+ abi: SETTLEMENT_ROUTER_ABI,
256
+ functionName: "isSettled",
257
+ args: [contextKey]
258
+ });
259
+ return isSettled;
260
+ } catch (error) {
261
+ throw new Error(
262
+ `Failed to check settlement status: ${error instanceof Error ? error.message : "Unknown error"}`
263
+ );
264
+ }
265
+ }
266
+ async function executeSettlementWithRouter(walletClient, params, config = {}) {
267
+ const gasLimit = config.gasLimit || calculateGasLimit("0x0", params.facilitatorFee, config.gasMultiplier);
268
+ console.log("[executeSettlementWithRouter] Settlement params:", {
269
+ token: params.token,
270
+ from: params.from,
271
+ value: params.value,
272
+ validAfter: params.validAfter,
273
+ validBefore: params.validBefore,
274
+ nonce: params.nonce,
275
+ signature: params.signature ? `${params.signature.slice(0, 10)}...` : void 0,
276
+ salt: params.salt,
277
+ payTo: params.payTo,
278
+ facilitatorFee: params.facilitatorFee,
279
+ hook: params.hook,
280
+ hookData: params.hookData,
281
+ settlementRouter: params.settlementRouter
282
+ });
283
+ try {
284
+ const txHash = await walletClient.writeContract({
285
+ address: params.settlementRouter,
286
+ abi: SETTLEMENT_ROUTER_ABI,
287
+ functionName: "settleAndExecute",
288
+ args: [
289
+ params.token,
290
+ params.from,
291
+ BigInt(params.value),
292
+ BigInt(params.validAfter),
293
+ BigInt(params.validBefore),
294
+ params.nonce,
295
+ params.signature,
296
+ params.salt,
297
+ params.payTo,
298
+ BigInt(params.facilitatorFee),
299
+ params.hook,
300
+ params.hookData
301
+ ],
302
+ gas: gasLimit,
303
+ chain: walletClient.chain,
304
+ account: walletClient.account ?? null
305
+ });
306
+ return txHash;
307
+ } catch (error) {
308
+ if (error instanceof Error) {
309
+ let errorMessage = `SettlementRouter execution failed: ${error.message}`;
310
+ if ("cause" in error && error.cause) {
311
+ errorMessage += ` (cause: ${error.cause})`;
312
+ }
313
+ throw new Error(errorMessage);
314
+ }
315
+ throw new Error("Unknown error during SettlementRouter execution");
316
+ }
317
+ }
318
+ async function waitForSettlementReceipt(publicClient, txHash, timeoutMs = 3e4) {
319
+ try {
320
+ const receipt = await publicClient.waitForTransactionReceipt({
321
+ hash: txHash,
322
+ timeout: timeoutMs
323
+ });
324
+ return {
325
+ success: receipt.status === "success",
326
+ blockNumber: receipt.blockNumber,
327
+ gasUsed: receipt.gasUsed,
328
+ effectiveGasPrice: receipt.effectiveGasPrice
329
+ };
330
+ } catch (error) {
331
+ throw new Error(
332
+ `Failed to get transaction receipt: ${error instanceof Error ? error.message : "Unknown error"}`
333
+ );
334
+ }
335
+ }
336
+ function parseEvmExactPayload(payload) {
337
+ const evmPayload = payload.payload;
338
+ if (!evmPayload || !evmPayload.signature || !evmPayload.authorization) {
339
+ throw new Error("Invalid EVM exact payload structure");
340
+ }
341
+ return evmPayload;
342
+ }
343
+ function parseSettlementRouterParams(paymentRequirements, paymentPayload) {
344
+ if (!isSettlementMode(paymentRequirements)) {
345
+ throw new Error("Payment requirements are not in SettlementRouter mode");
346
+ }
347
+ const evmPayload = parseEvmExactPayload(paymentPayload);
348
+ const extra = parseSettlementExtra(paymentRequirements.extra);
349
+ return {
350
+ token: paymentRequirements.asset,
351
+ from: evmPayload.authorization.from,
352
+ value: paymentRequirements.amount,
353
+ // V2 uses 'amount', not 'maxAmountRequired'
354
+ validAfter: evmPayload.authorization.validAfter || "0x0",
355
+ validBefore: evmPayload.authorization.validBefore || "0xFFFFFFFFFFFFFFFF",
356
+ nonce: evmPayload.authorization.nonce,
357
+ signature: evmPayload.signature,
358
+ salt: extra.salt,
359
+ payTo: extra.payTo,
360
+ facilitatorFee: extra.facilitatorFee,
361
+ hook: extra.hook,
362
+ hookData: extra.hookData,
363
+ settlementRouter: extra.settlementRouter
364
+ };
365
+ }
366
+ async function executeSettlementWithWalletClient(walletClient, publicClient, paymentRequirements, paymentPayload, config = {}) {
367
+ try {
368
+ const canonicalNetwork = toCanonicalNetworkKey(paymentRequirements.network);
369
+ const v1NetworkName = getNetworkName(canonicalNetwork);
370
+ const networkConfig = getNetworkConfig(v1NetworkName);
371
+ const settlementRouter = paymentRequirements.extra?.settlementRouter;
372
+ if (!settlementRouter) {
373
+ throw new Error("Missing settlementRouter in payment requirements");
374
+ }
375
+ validateSettlementRouter(
376
+ paymentRequirements.network,
377
+ settlementRouter,
378
+ config.allowedRouters,
379
+ networkConfig
380
+ );
381
+ const params = parseSettlementRouterParams(paymentRequirements, paymentPayload);
382
+ const txHash = await executeSettlementWithRouter(walletClient, params, {
383
+ gasLimit: config.gasLimit,
384
+ gasMultiplier: config.gasMultiplier
385
+ });
386
+ const receipt = await waitForSettlementReceipt(publicClient, txHash, config.timeoutMs || 3e4);
387
+ return {
388
+ success: receipt.success,
389
+ transaction: txHash,
390
+ network: paymentRequirements.network,
391
+ payer: params.from,
392
+ // Use params.from for consistency
393
+ errorReason: receipt.success ? void 0 : "Transaction failed"
394
+ };
395
+ } catch (error) {
396
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
397
+ let payer;
398
+ try {
399
+ const params = parseSettlementRouterParams(paymentRequirements, paymentPayload);
400
+ payer = params.from;
401
+ } catch (parseError) {
402
+ console.error("[executeSettlementWithWalletClient] Failed to parse params:", parseError);
403
+ try {
404
+ const evmPayload = parseEvmExactPayload(paymentPayload);
405
+ payer = evmPayload.authorization.from;
406
+ } catch {
407
+ payer = void 0;
408
+ }
409
+ }
410
+ console.error("[executeSettlementWithWalletClient] Settlement failed:", {
411
+ error: errorMessage,
412
+ stack: error instanceof Error ? error.stack : void 0,
413
+ network: paymentRequirements.network,
414
+ asset: paymentRequirements.asset,
415
+ payer
416
+ });
417
+ return {
418
+ success: false,
419
+ transaction: "",
420
+ network: paymentRequirements.network,
421
+ payer,
422
+ errorReason: errorMessage
423
+ };
424
+ }
425
+ }
426
+ async function settleWithSettlementRouter(paymentRequirements, paymentPayload, config, options = {}) {
427
+ try {
428
+ const networkConfig = getNetworkConfig(paymentRequirements.network);
429
+ validateSettlementRouter(
430
+ paymentRequirements.network,
431
+ paymentRequirements.extra?.settlementRouter,
432
+ config.allowedRouters,
433
+ networkConfig
434
+ );
435
+ const params = parseSettlementRouterParams(paymentRequirements, paymentPayload);
436
+ const publicClient = createPublicClientForNetwork(paymentRequirements.network, config.rpcUrls);
437
+ const walletClient = createWalletClientForNetwork(
438
+ paymentRequirements.network,
439
+ config.signer,
440
+ config.rpcUrls,
441
+ void 0,
442
+ config.privateKey
443
+ );
444
+ const txHash = await executeSettlementWithRouter(walletClient, params, {
445
+ gasLimit: options.gasLimit,
446
+ gasMultiplier: options.gasMultiplier
447
+ });
448
+ const receipt = await waitForSettlementReceipt(
449
+ publicClient,
450
+ txHash,
451
+ options.timeoutMs || 3e4
452
+ );
453
+ return {
454
+ success: receipt.success,
455
+ transaction: txHash,
456
+ network: paymentRequirements.network,
457
+ payer: params.from,
458
+ errorReason: receipt.success ? void 0 : "Transaction failed"
459
+ };
460
+ } catch (error) {
461
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
462
+ let payer;
463
+ try {
464
+ const evmPayload = parseEvmExactPayload(paymentPayload);
465
+ payer = evmPayload.authorization.from;
466
+ } catch {
467
+ payer = void 0;
468
+ }
469
+ return {
470
+ success: false,
471
+ transaction: "",
472
+ network: paymentRequirements.network,
473
+ payer,
474
+ errorReason: errorMessage
475
+ };
476
+ }
477
+ }
478
+ var authorizationTypes = {
479
+ TransferWithAuthorization: [
480
+ { name: "from", type: "address" },
481
+ { name: "to", type: "address" },
482
+ { name: "value", type: "uint256" },
483
+ { name: "validAfter", type: "uint256" },
484
+ { name: "validBefore", type: "uint256" },
485
+ { name: "nonce", type: "bytes32" }
486
+ ]
487
+ };
488
+ function parseEvmExactPayload2(payload) {
489
+ const evmPayload = payload.payload;
490
+ if (!evmPayload.signature) {
491
+ throw new FacilitatorValidationError("Missing signature in EVM exact payload");
492
+ }
493
+ if (!evmPayload.authorization) {
494
+ throw new FacilitatorValidationError("Missing authorization in EVM exact payload");
495
+ }
496
+ const auth = evmPayload.authorization;
497
+ if (!auth.from || !auth.to || !auth.value || !auth.nonce) {
498
+ throw new FacilitatorValidationError("Invalid authorization structure in EVM exact payload");
499
+ }
500
+ return evmPayload;
501
+ }
502
+ var eip3009ABI = [
503
+ {
504
+ inputs: [
505
+ { name: "from", type: "address" },
506
+ { name: "to", type: "address" },
507
+ { name: "value", type: "uint256" },
508
+ { name: "validAfter", type: "uint256" },
509
+ { name: "validBefore", type: "uint256" },
510
+ { name: "nonce", type: "bytes32" },
511
+ { name: "signature", type: "bytes" }
512
+ ],
513
+ name: "transferWithAuthorization",
514
+ outputs: [],
515
+ stateMutability: "nonpayable",
516
+ type: "function"
517
+ },
518
+ {
519
+ inputs: [{ name: "account", type: "address" }],
520
+ name: "balanceOf",
521
+ outputs: [{ name: "", type: "uint256" }],
522
+ stateMutability: "view",
523
+ type: "function"
524
+ }
525
+ ];
526
+ var RouterSettlementFacilitator = class {
527
+ constructor(config) {
528
+ this.scheme = "exact";
529
+ this.caipFamily = "eip155:*";
530
+ validateFacilitatorConfig(config);
531
+ this.config = {
532
+ // Default values
533
+ gasConfig: {
534
+ maxGasLimit: 5000000n,
535
+ gasMultiplier: 1.2
536
+ },
537
+ feeConfig: {
538
+ minFee: "0x0",
539
+ maxFee: "0xFFFFFFFFFFFFFFFF"
540
+ },
541
+ timeouts: {
542
+ verify: 5e3,
543
+ // 5 seconds
544
+ settle: 3e4
545
+ // 30 seconds
546
+ },
547
+ // Override with user config
548
+ ...config
549
+ };
550
+ }
551
+ /**
552
+ * Get scheme-specific extra data for responses
553
+ */
554
+ getExtra(network) {
555
+ try {
556
+ if (!network || typeof network !== "string" || network.trim() === "") {
557
+ return void 0;
558
+ }
559
+ const networkConfig = getNetworkConfig(network);
560
+ if (!networkConfig) {
561
+ return void 0;
562
+ }
563
+ return {
564
+ scheme: this.scheme,
565
+ caipFamily: this.caipFamily,
566
+ settlementRouter: networkConfig?.settlementRouter,
567
+ defaultAsset: networkConfig?.defaultAsset,
568
+ supportedNetworks: [network]
569
+ // Can be expanded for multi-network support
570
+ };
571
+ } catch (error) {
572
+ return void 0;
573
+ }
574
+ }
575
+ /**
576
+ * Get signer addresses for the network
577
+ * Derives from privateKey if signer address not explicitly provided
578
+ */
579
+ getSigners(network) {
580
+ validateNetwork(network);
581
+ if (this.config.signer) {
582
+ return [this.config.signer];
583
+ }
584
+ if (this.config.privateKey) {
585
+ const account = privateKeyToAccount(this.config.privateKey);
586
+ return [account.address];
587
+ }
588
+ throw new Error("Either signer or privateKey must be provided in FacilitatorConfig");
589
+ }
590
+ /**
591
+ * Verify payment payload without executing settlement
592
+ */
593
+ async verify(payload, requirements) {
594
+ try {
595
+ this.validateBasicPayload(payload, requirements);
596
+ const isRouterSettlement = isSettlementMode(requirements);
597
+ if (isRouterSettlement) {
598
+ return await this.verifySettlementRouter(payload, requirements);
599
+ } else {
600
+ return await this.verifyStandard(payload, requirements);
601
+ }
602
+ } catch (error) {
603
+ let payer;
604
+ try {
605
+ const evmPayload = parseEvmExactPayload2(payload);
606
+ payer = evmPayload.authorization.from;
607
+ } catch {
608
+ payer = void 0;
609
+ }
610
+ if (error instanceof FacilitatorValidationError || error instanceof SettlementRouterError) {
611
+ return {
612
+ isValid: false,
613
+ invalidReason: error.message,
614
+ payer: payer || ""
615
+ };
616
+ }
617
+ return {
618
+ isValid: false,
619
+ invalidReason: `Verification failed: ${error instanceof Error ? error.message : "Unknown error"}`,
620
+ payer: payer || ""
621
+ };
622
+ }
623
+ }
624
+ /**
625
+ * Settle payment by executing blockchain transaction
626
+ */
627
+ async settle(payload, requirements) {
628
+ try {
629
+ const verification = await this.verify(payload, requirements);
630
+ if (!verification.isValid) {
631
+ return {
632
+ success: false,
633
+ transaction: "",
634
+ network: requirements.network,
635
+ payer: verification.payer,
636
+ errorReason: verification.invalidReason || "Payment verification failed"
637
+ };
638
+ }
639
+ const isRouterSettlement = isSettlementMode(requirements);
640
+ if (isRouterSettlement) {
641
+ return await this.settleWithRouter(payload, requirements);
642
+ } else {
643
+ return await this.settleStandard(payload, requirements);
644
+ }
645
+ } catch (error) {
646
+ let payer;
647
+ try {
648
+ const evmPayload = parseEvmExactPayload2(payload);
649
+ payer = evmPayload.authorization.from;
650
+ } catch {
651
+ payer = void 0;
652
+ }
653
+ return {
654
+ success: false,
655
+ transaction: "",
656
+ network: requirements.network,
657
+ payer: payer || "",
658
+ errorReason: error instanceof Error ? error.message : "Unknown settlement error"
659
+ };
660
+ }
661
+ }
662
+ /**
663
+ * Validate basic payload and requirements
664
+ */
665
+ validateBasicPayload(payload, requirements) {
666
+ validateNetwork(requirements.network);
667
+ if (requirements.scheme !== this.scheme) {
668
+ throw new FacilitatorValidationError(
669
+ `Scheme mismatch: expected ${this.scheme}, got ${requirements.scheme}`
670
+ );
671
+ }
672
+ if (!requirements.network.startsWith("eip155:")) {
673
+ throw new FacilitatorValidationError(
674
+ `Unsupported network family: ${requirements.network}. Only EVM networks (eip155:*) are supported`
675
+ );
676
+ }
677
+ parseEvmExactPayload2(payload);
678
+ if (!requirements.asset) {
679
+ throw new FacilitatorValidationError("Missing asset in payment requirements");
680
+ }
681
+ if (!requirements.payTo) {
682
+ throw new FacilitatorValidationError("Missing payTo address in payment requirements");
683
+ }
684
+ if (!requirements.amount) {
685
+ throw new FacilitatorValidationError("Missing amount in payment requirements");
686
+ }
687
+ }
688
+ /**
689
+ * Verify payment for SettlementRouter mode
690
+ */
691
+ async verifySettlementRouter(payload, requirements) {
692
+ const evmPayload = parseEvmExactPayload2(payload);
693
+ const payer = evmPayload.authorization.from;
694
+ const settlementExtra = validateSettlementExtra(requirements.extra);
695
+ const networkConfig = getNetworkConfig(requirements.network);
696
+ validateSettlementRouter(
697
+ requirements.network,
698
+ settlementExtra.settlementRouter,
699
+ this.config.allowedRouters,
700
+ networkConfig
701
+ );
702
+ validateFeeAmount(
703
+ settlementExtra.facilitatorFee,
704
+ this.config.feeConfig?.minFee,
705
+ this.config.feeConfig?.maxFee
706
+ );
707
+ const publicClient = createPublicClientForNetwork(requirements.network, this.config.rpcUrls);
708
+ const extraWithDomain = requirements.extra;
709
+ const eip712Name = extraWithDomain?.name || "USD Coin";
710
+ const eip712Version = extraWithDomain?.version || "2";
711
+ try {
712
+ const parsedSignature = parseErc6492Signature(evmPayload.signature);
713
+ const typedData = {
714
+ types: authorizationTypes,
715
+ primaryType: "TransferWithAuthorization",
716
+ domain: {
717
+ name: eip712Name,
718
+ version: eip712Version,
719
+ chainId: parseInt(requirements.network.split(":")[1]),
720
+ verifyingContract: requirements.asset
721
+ },
722
+ message: {
723
+ from: payer,
724
+ to: evmPayload.authorization.to,
725
+ value: BigInt(evmPayload.authorization.value),
726
+ validAfter: BigInt(evmPayload.authorization.validAfter || "0x0"),
727
+ validBefore: BigInt(evmPayload.authorization.validBefore || "0xFFFFFFFFFFFFFFFF"),
728
+ nonce: evmPayload.authorization.nonce
729
+ }
730
+ };
731
+ const isValidSignature = await verifyTypedData({
732
+ address: payer,
733
+ ...typedData,
734
+ signature: parsedSignature.signature
735
+ });
736
+ if (!isValidSignature) {
737
+ return {
738
+ isValid: false,
739
+ invalidReason: "Invalid signature",
740
+ payer
741
+ };
742
+ }
743
+ } catch (error) {
744
+ return {
745
+ isValid: false,
746
+ invalidReason: `Signature verification failed: ${error instanceof Error ? error.message : "Unknown error"}`,
747
+ payer
748
+ };
749
+ }
750
+ try {
751
+ const chainId = parseInt(requirements.network.split(":")[1]);
752
+ const calculatedCommitment = calculateCommitment({
753
+ chainId,
754
+ hub: settlementExtra.settlementRouter,
755
+ asset: requirements.asset,
756
+ from: payer,
757
+ value: evmPayload.authorization.value,
758
+ validAfter: evmPayload.authorization.validAfter || "0x0",
759
+ validBefore: evmPayload.authorization.validBefore || "0xFFFFFFFFFFFFFFFF",
760
+ salt: settlementExtra.salt,
761
+ payTo: settlementExtra.payTo,
762
+ facilitatorFee: settlementExtra.facilitatorFee,
763
+ hook: settlementExtra.hook,
764
+ hookData: settlementExtra.hookData
765
+ });
766
+ if (evmPayload.authorization.nonce !== calculatedCommitment) {
767
+ return {
768
+ isValid: false,
769
+ invalidReason: "Commitment mismatch: nonce does not match calculated commitment",
770
+ payer
771
+ };
772
+ }
773
+ } catch (error) {
774
+ return {
775
+ isValid: false,
776
+ invalidReason: `Commitment verification failed: ${error instanceof Error ? error.message : "Unknown error"}`,
777
+ payer
778
+ };
779
+ }
780
+ try {
781
+ const balance = await publicClient.readContract({
782
+ address: requirements.asset,
783
+ abi: [
784
+ {
785
+ type: "function",
786
+ name: "balanceOf",
787
+ inputs: [{ name: "account", type: "address" }],
788
+ outputs: [{ name: "", type: "uint256" }],
789
+ stateMutability: "view"
790
+ }
791
+ ],
792
+ functionName: "balanceOf",
793
+ args: [payer]
794
+ });
795
+ const totalRequired = BigInt(requirements.amount) + BigInt(settlementExtra.facilitatorFee);
796
+ if (balance < totalRequired) {
797
+ return {
798
+ isValid: false,
799
+ invalidReason: `Insufficient balance: have ${balance}, need ${totalRequired}`,
800
+ payer
801
+ };
802
+ }
803
+ } catch (error) {
804
+ return {
805
+ isValid: false,
806
+ invalidReason: `Balance check failed: ${error instanceof Error ? error.message : "Unknown error"}`,
807
+ payer
808
+ };
809
+ }
810
+ return {
811
+ isValid: true,
812
+ payer
813
+ };
814
+ }
815
+ /**
816
+ * Verify payment for standard mode (fallback)
817
+ */
818
+ async verifyStandard(payload, requirements) {
819
+ const evmPayload = parseEvmExactPayload2(payload);
820
+ const payer = evmPayload.authorization.from;
821
+ const publicClient = createPublicClientForNetwork(requirements.network, this.config.rpcUrls);
822
+ try {
823
+ const parsedSignature = parseErc6492Signature(evmPayload.signature);
824
+ const extraAny = requirements.extra;
825
+ const domainName = extraAny?.name || "USD Coin";
826
+ const domainVersion = extraAny?.version || "3";
827
+ const typedData = {
828
+ types: authorizationTypes,
829
+ primaryType: "TransferWithAuthorization",
830
+ domain: {
831
+ name: domainName,
832
+ version: domainVersion,
833
+ chainId: parseInt(requirements.network.split(":")[1]),
834
+ verifyingContract: requirements.asset
835
+ },
836
+ message: {
837
+ from: payer,
838
+ to: requirements.payTo,
839
+ value: BigInt(requirements.amount),
840
+ validAfter: BigInt(evmPayload.authorization.validAfter || "0x0"),
841
+ validBefore: BigInt(evmPayload.authorization.validBefore || "0xFFFFFFFFFFFFFFFF"),
842
+ nonce: evmPayload.authorization.nonce
843
+ }
844
+ };
845
+ const isValidSignature = await verifyTypedData({
846
+ address: payer,
847
+ ...typedData,
848
+ signature: parsedSignature.signature
849
+ });
850
+ if (!isValidSignature) {
851
+ return {
852
+ isValid: false,
853
+ invalidReason: "Invalid signature",
854
+ payer
855
+ };
856
+ }
857
+ const balance = await publicClient.readContract({
858
+ address: requirements.asset,
859
+ abi: eip3009ABI,
860
+ functionName: "balanceOf",
861
+ args: [payer]
862
+ });
863
+ if (BigInt(balance) < BigInt(requirements.amount)) {
864
+ return {
865
+ isValid: false,
866
+ invalidReason: "Insufficient balance",
867
+ payer
868
+ };
869
+ }
870
+ return {
871
+ isValid: true,
872
+ payer
873
+ };
874
+ } catch (error) {
875
+ return {
876
+ isValid: false,
877
+ invalidReason: `Standard verification failed: ${error instanceof Error ? error.message : "Unknown error"}`,
878
+ payer
879
+ };
880
+ }
881
+ }
882
+ /**
883
+ * Settle payment using SettlementRouter
884
+ */
885
+ async settleWithRouter(payload, requirements) {
886
+ return await settleWithSettlementRouter(requirements, payload, this.config, {
887
+ gasMultiplier: this.config.gasConfig?.gasMultiplier,
888
+ timeoutMs: this.config.timeouts?.settle
889
+ });
890
+ }
891
+ /**
892
+ * Settle payment using standard method (fallback)
893
+ */
894
+ async settleStandard(payload, requirements) {
895
+ const evmPayload = parseEvmExactPayload2(payload);
896
+ const payer = evmPayload.authorization.from;
897
+ const walletClient = createWalletClientForNetwork(
898
+ requirements.network,
899
+ this.config.signer,
900
+ this.config.rpcUrls,
901
+ void 0,
902
+ this.config.privateKey
903
+ );
904
+ const publicClient = createPublicClientForNetwork(requirements.network, this.config.rpcUrls);
905
+ try {
906
+ const parsedSignature = parseErc6492Signature(evmPayload.signature);
907
+ const txHash = await walletClient.writeContract({
908
+ address: requirements.asset,
909
+ abi: eip3009ABI,
910
+ functionName: "transferWithAuthorization",
911
+ args: [
912
+ payer,
913
+ requirements.payTo,
914
+ BigInt(requirements.amount),
915
+ BigInt(evmPayload.authorization.validAfter || "0x0"),
916
+ BigInt(evmPayload.authorization.validBefore || "0xFFFFFFFFFFFFFFFF"),
917
+ evmPayload.authorization.nonce,
918
+ parsedSignature.signature
919
+ ],
920
+ chain: walletClient.chain,
921
+ account: walletClient.account ?? null
922
+ });
923
+ const receipt = await waitForSettlementReceipt(publicClient, txHash);
924
+ return {
925
+ success: receipt.success,
926
+ transaction: txHash,
927
+ network: requirements.network,
928
+ payer,
929
+ errorReason: receipt.success ? void 0 : "Transaction failed"
930
+ };
931
+ } catch (error) {
932
+ return {
933
+ success: false,
934
+ transaction: "",
935
+ network: requirements.network,
936
+ payer,
937
+ errorReason: error instanceof Error ? error.message : "Unknown error"
938
+ };
939
+ }
940
+ }
941
+ };
942
+ function createRouterSettlementFacilitator(config) {
943
+ return new RouterSettlementFacilitator(config);
944
+ }
945
+
946
+ export { RouterSettlementFacilitator, calculateGasLimit, checkIfSettled, createPublicClientForNetwork, createRouterSettlementFacilitator, createWalletClientForNetwork, executeSettlementWithRouter, executeSettlementWithWalletClient, isValid256BitHex, isValid32ByteHex, isValidEthereumAddress, isValidHex, parseSettlementRouterParams, settleWithSettlementRouter, validateFacilitatorConfig, validateFeeAmount, validateGasLimit, validateGasMultiplier, validateNetwork, validateSettlementExtra, validateSettlementRouter, waitForSettlementReceipt };
947
+ //# sourceMappingURL=index.mjs.map
948
+ //# sourceMappingURL=index.mjs.map