@x402x/extensions 2.0.0 → 2.1.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.d.mts +359 -180
- package/dist/index.d.ts +359 -180
- package/dist/index.js +547 -345
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +543 -330
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { defineChain, encodeAbiParameters, keccak256, encodePacked } from 'viem';
|
|
1
|
+
import { defineChain, encodeAbiParameters, keccak256, encodePacked, getAddress } from 'viem';
|
|
2
2
|
import * as allChains from 'viem/chains';
|
|
3
|
+
import { decodePaymentSignatureHeader } from '@x402/core/http';
|
|
3
4
|
|
|
4
5
|
// src/types.ts
|
|
5
6
|
var SettlementExtraError = class extends Error {
|
|
@@ -175,7 +176,8 @@ function isValidHex(hex) {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
// src/network-utils.ts
|
|
178
|
-
var
|
|
179
|
+
var NETWORK_ALIASES_V1_TO_V2 = {
|
|
180
|
+
// V1 human-readable names -> V2 CAIP-2 canonical keys
|
|
179
181
|
"base-sepolia": "eip155:84532",
|
|
180
182
|
"x-layer-testnet": "eip155:1952",
|
|
181
183
|
"skale-base-sepolia": "eip155:324705682",
|
|
@@ -184,15 +186,12 @@ var NETWORK_IDS = {
|
|
|
184
186
|
"bsc-testnet": "eip155:97",
|
|
185
187
|
"bsc": "eip155:56"
|
|
186
188
|
};
|
|
187
|
-
var
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"eip155:97": "bsc-testnet",
|
|
194
|
-
"eip155:56": "bsc"
|
|
195
|
-
};
|
|
189
|
+
var NETWORK_ALIASES = Object.entries(
|
|
190
|
+
NETWORK_ALIASES_V1_TO_V2
|
|
191
|
+
).reduce((acc, [name, caip2]) => {
|
|
192
|
+
acc[caip2] = name;
|
|
193
|
+
return acc;
|
|
194
|
+
}, {});
|
|
196
195
|
var DEFAULT_ASSETS = {
|
|
197
196
|
"eip155:84532": {
|
|
198
197
|
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
@@ -251,23 +250,14 @@ var DEFAULT_ASSETS = {
|
|
|
251
250
|
}
|
|
252
251
|
}
|
|
253
252
|
};
|
|
254
|
-
function
|
|
255
|
-
const
|
|
256
|
-
if (!
|
|
257
|
-
throw new Error(
|
|
258
|
-
`Unsupported network: ${networkName}. Supported networks: ${Object.keys(NETWORK_IDS).join(", ")}`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
return networkId;
|
|
262
|
-
}
|
|
263
|
-
function getNetworkName(network) {
|
|
264
|
-
const networkName = NETWORK_NAMES[network];
|
|
265
|
-
if (!networkName) {
|
|
253
|
+
function getNetworkAlias(network) {
|
|
254
|
+
const networkAlias = NETWORK_ALIASES[network];
|
|
255
|
+
if (!networkAlias) {
|
|
266
256
|
throw new Error(
|
|
267
|
-
`Unsupported network ID: ${network}. Supported network IDs: ${Object.keys(
|
|
257
|
+
`Unsupported network ID: ${network}. Supported network IDs: ${Object.keys(NETWORK_ALIASES).join(", ")}`
|
|
268
258
|
);
|
|
269
259
|
}
|
|
270
|
-
return
|
|
260
|
+
return networkAlias;
|
|
271
261
|
}
|
|
272
262
|
function getDefaultAsset(network) {
|
|
273
263
|
const assetInfo = DEFAULT_ASSETS[network];
|
|
@@ -302,21 +292,8 @@ function processPriceToAtomicAmount(price, network) {
|
|
|
302
292
|
};
|
|
303
293
|
}
|
|
304
294
|
}
|
|
305
|
-
var NETWORK_ALIASES_V1_TO_V2 = {
|
|
306
|
-
// V1 human-readable names -> V2 CAIP-2 canonical keys
|
|
307
|
-
"base-sepolia": "eip155:84532",
|
|
308
|
-
"x-layer-testnet": "eip155:1952",
|
|
309
|
-
"skale-base-sepolia": "eip155:324705682",
|
|
310
|
-
"base": "eip155:8453",
|
|
311
|
-
"x-layer": "eip155:196",
|
|
312
|
-
"bsc-testnet": "eip155:97",
|
|
313
|
-
"bsc": "eip155:56"
|
|
314
|
-
};
|
|
315
295
|
function getSupportedNetworkIds() {
|
|
316
|
-
return Object.keys(
|
|
317
|
-
}
|
|
318
|
-
function getSupportedNetworksV2() {
|
|
319
|
-
return getSupportedNetworkIds();
|
|
296
|
+
return Object.keys(NETWORK_ALIASES);
|
|
320
297
|
}
|
|
321
298
|
function getNetworkAliasesV1ToV2() {
|
|
322
299
|
return { ...NETWORK_ALIASES_V1_TO_V2 };
|
|
@@ -324,11 +301,11 @@ function getNetworkAliasesV1ToV2() {
|
|
|
324
301
|
function toCanonicalNetworkKey(network) {
|
|
325
302
|
if (network.startsWith("eip155:")) {
|
|
326
303
|
const canonicalNetwork2 = network;
|
|
327
|
-
if (canonicalNetwork2 in
|
|
304
|
+
if (canonicalNetwork2 in NETWORK_ALIASES) {
|
|
328
305
|
return canonicalNetwork2;
|
|
329
306
|
}
|
|
330
307
|
throw new Error(
|
|
331
|
-
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(
|
|
308
|
+
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES).join(", ")}`
|
|
332
309
|
);
|
|
333
310
|
}
|
|
334
311
|
const canonicalNetwork = NETWORK_ALIASES_V1_TO_V2[network];
|
|
@@ -352,15 +329,33 @@ function getDefaultAssetConfig(network) {
|
|
|
352
329
|
}
|
|
353
330
|
};
|
|
354
331
|
}
|
|
332
|
+
function normalizeToCAIP2(network) {
|
|
333
|
+
if (network.startsWith("eip155:")) {
|
|
334
|
+
const caip22 = network;
|
|
335
|
+
if (!(caip22 in networks)) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(networks).join(", ")}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return caip22;
|
|
341
|
+
}
|
|
342
|
+
const caip2 = NETWORK_ALIASES_V1_TO_V2[network];
|
|
343
|
+
if (!caip2) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Unknown network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES_V1_TO_V2).join(", ")}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return caip2;
|
|
349
|
+
}
|
|
355
350
|
var networks = {
|
|
356
|
-
"
|
|
357
|
-
chainId:
|
|
351
|
+
"eip155:84532": {
|
|
352
|
+
chainId: 84532,
|
|
358
353
|
name: "Base Sepolia",
|
|
359
354
|
type: "testnet",
|
|
360
355
|
addressExplorerBaseUrl: "https://sepolia.basescan.org/address/",
|
|
361
356
|
txExplorerBaseUrl: "https://sepolia.basescan.org/tx/",
|
|
362
357
|
settlementRouter: "0x817e4f0ee2fbdaac426f1178e149f7dc98873ecb",
|
|
363
|
-
defaultAsset: getDefaultAssetConfig(
|
|
358
|
+
defaultAsset: getDefaultAssetConfig("eip155:84532"),
|
|
364
359
|
hooks: {
|
|
365
360
|
transfer: "0x4DE234059C6CcC94B8fE1eb1BD24804794083569"
|
|
366
361
|
},
|
|
@@ -375,14 +370,14 @@ var networks = {
|
|
|
375
370
|
nativeToken: "ETH"
|
|
376
371
|
}
|
|
377
372
|
},
|
|
378
|
-
"
|
|
379
|
-
chainId:
|
|
373
|
+
"eip155:1952": {
|
|
374
|
+
chainId: 1952,
|
|
380
375
|
name: "X Layer Testnet",
|
|
381
376
|
type: "testnet",
|
|
382
377
|
addressExplorerBaseUrl: "https://www.oklink.com/xlayer-test/address/",
|
|
383
378
|
txExplorerBaseUrl: "https://www.oklink.com/xlayer-test/tx/",
|
|
384
379
|
settlementRouter: "0xba9980fb08771e2fd10c17450f52d39bcb9ed576",
|
|
385
|
-
defaultAsset: getDefaultAssetConfig(
|
|
380
|
+
defaultAsset: getDefaultAssetConfig("eip155:1952"),
|
|
386
381
|
hooks: {
|
|
387
382
|
transfer: "0xD4b98dd614c1Ea472fC4547a5d2B93f3D3637BEE"
|
|
388
383
|
},
|
|
@@ -397,14 +392,14 @@ var networks = {
|
|
|
397
392
|
nativeToken: "OKB"
|
|
398
393
|
}
|
|
399
394
|
},
|
|
400
|
-
"
|
|
401
|
-
chainId:
|
|
395
|
+
"eip155:324705682": {
|
|
396
|
+
chainId: 324705682,
|
|
402
397
|
name: "SKALE Base Sepolia",
|
|
403
398
|
type: "testnet",
|
|
404
399
|
addressExplorerBaseUrl: "https://base-sepolia-testnet-explorer.skalenodes.com/address/",
|
|
405
400
|
txExplorerBaseUrl: "https://base-sepolia-testnet-explorer.skalenodes.com/tx/",
|
|
406
401
|
settlementRouter: "0x1Ae0E196dC18355aF3a19985faf67354213F833D",
|
|
407
|
-
defaultAsset: getDefaultAssetConfig(
|
|
402
|
+
defaultAsset: getDefaultAssetConfig("eip155:324705682"),
|
|
408
403
|
hooks: {
|
|
409
404
|
transfer: "0x2f05fe5674aE756E25C26855258B4877E9e021Fd"
|
|
410
405
|
},
|
|
@@ -419,14 +414,14 @@ var networks = {
|
|
|
419
414
|
nativeToken: "Credits"
|
|
420
415
|
}
|
|
421
416
|
},
|
|
422
|
-
"
|
|
423
|
-
chainId:
|
|
417
|
+
"eip155:97": {
|
|
418
|
+
chainId: 97,
|
|
424
419
|
name: "BSC Testnet",
|
|
425
420
|
type: "testnet",
|
|
426
421
|
addressExplorerBaseUrl: "https://testnet.bscscan.com/address/",
|
|
427
422
|
txExplorerBaseUrl: "https://testnet.bscscan.com/tx/",
|
|
428
423
|
settlementRouter: "0x1Ae0E196dC18355aF3a19985faf67354213F833D",
|
|
429
|
-
defaultAsset: getDefaultAssetConfig(
|
|
424
|
+
defaultAsset: getDefaultAssetConfig("eip155:97"),
|
|
430
425
|
hooks: {
|
|
431
426
|
transfer: "0x2f05fe5674aE756E25C26855258B4877E9e021Fd"
|
|
432
427
|
},
|
|
@@ -442,14 +437,14 @@ var networks = {
|
|
|
442
437
|
}
|
|
443
438
|
},
|
|
444
439
|
// Mainnet configurations
|
|
445
|
-
|
|
446
|
-
chainId:
|
|
440
|
+
"eip155:8453": {
|
|
441
|
+
chainId: 8453,
|
|
447
442
|
name: "Base Mainnet",
|
|
448
443
|
type: "mainnet",
|
|
449
444
|
addressExplorerBaseUrl: "https://basescan.org/address/",
|
|
450
445
|
txExplorerBaseUrl: "https://basescan.org/tx/",
|
|
451
446
|
settlementRouter: "0x73fc659Cd5494E69852bE8D9D23FE05Aab14b29B",
|
|
452
|
-
defaultAsset: getDefaultAssetConfig(
|
|
447
|
+
defaultAsset: getDefaultAssetConfig("eip155:8453"),
|
|
453
448
|
hooks: {
|
|
454
449
|
transfer: "0x081258287F692D61575387ee2a4075f34dd7Aef7"
|
|
455
450
|
},
|
|
@@ -464,14 +459,14 @@ var networks = {
|
|
|
464
459
|
nativeToken: "ETH"
|
|
465
460
|
}
|
|
466
461
|
},
|
|
467
|
-
"
|
|
468
|
-
chainId:
|
|
462
|
+
"eip155:196": {
|
|
463
|
+
chainId: 196,
|
|
469
464
|
name: "X Layer Mainnet",
|
|
470
465
|
type: "mainnet",
|
|
471
466
|
addressExplorerBaseUrl: "https://www.oklink.com/xlayer/address/",
|
|
472
467
|
txExplorerBaseUrl: "https://www.oklink.com/xlayer/tx/",
|
|
473
468
|
settlementRouter: "0x73fc659Cd5494E69852bE8D9D23FE05Aab14b29B",
|
|
474
|
-
defaultAsset: getDefaultAssetConfig(
|
|
469
|
+
defaultAsset: getDefaultAssetConfig("eip155:196"),
|
|
475
470
|
hooks: {
|
|
476
471
|
transfer: "0x081258287F692D61575387ee2a4075f34dd7Aef7"
|
|
477
472
|
},
|
|
@@ -486,14 +481,14 @@ var networks = {
|
|
|
486
481
|
nativeToken: "OKB"
|
|
487
482
|
}
|
|
488
483
|
},
|
|
489
|
-
|
|
490
|
-
chainId:
|
|
484
|
+
"eip155:56": {
|
|
485
|
+
chainId: 56,
|
|
491
486
|
name: "BSC Mainnet",
|
|
492
487
|
type: "mainnet",
|
|
493
488
|
addressExplorerBaseUrl: "https://bscscan.com/address/",
|
|
494
489
|
txExplorerBaseUrl: "https://bscscan.com/tx/",
|
|
495
490
|
settlementRouter: "0x1Ae0E196dC18355aF3a19985faf67354213F833D",
|
|
496
|
-
defaultAsset: getDefaultAssetConfig(
|
|
491
|
+
defaultAsset: getDefaultAssetConfig("eip155:56"),
|
|
497
492
|
hooks: {
|
|
498
493
|
transfer: "0x2f05fe5674aE756E25C26855258B4877E9e021Fd"
|
|
499
494
|
},
|
|
@@ -510,7 +505,8 @@ var networks = {
|
|
|
510
505
|
}
|
|
511
506
|
};
|
|
512
507
|
function getNetworkConfig(network) {
|
|
513
|
-
const
|
|
508
|
+
const caip2Network = normalizeToCAIP2(network);
|
|
509
|
+
const config = networks[caip2Network];
|
|
514
510
|
if (!config) {
|
|
515
511
|
throw new Error(
|
|
516
512
|
`Unsupported network: ${network}. Supported networks: ${Object.keys(networks).join(", ")}`
|
|
@@ -519,13 +515,18 @@ function getNetworkConfig(network) {
|
|
|
519
515
|
return config;
|
|
520
516
|
}
|
|
521
517
|
function isNetworkSupported(network) {
|
|
522
|
-
|
|
518
|
+
try {
|
|
519
|
+
const caip2Network = normalizeToCAIP2(network);
|
|
520
|
+
return caip2Network in networks;
|
|
521
|
+
} catch {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
523
524
|
}
|
|
524
|
-
function
|
|
525
|
-
return Object.keys(networks);
|
|
525
|
+
function getSupportedNetworkAliases() {
|
|
526
|
+
return Object.keys(networks).map((caip2) => getNetworkAlias(caip2));
|
|
526
527
|
}
|
|
527
528
|
function getSupportedNetworks() {
|
|
528
|
-
return
|
|
529
|
+
return Object.keys(networks);
|
|
529
530
|
}
|
|
530
531
|
var customChains = {
|
|
531
532
|
// X Layer Testnet
|
|
@@ -574,15 +575,31 @@ var customChains = {
|
|
|
574
575
|
})
|
|
575
576
|
};
|
|
576
577
|
function getChain(network) {
|
|
577
|
-
|
|
578
|
-
|
|
578
|
+
let chainId;
|
|
579
|
+
if (network.startsWith("eip155:")) {
|
|
580
|
+
const caip2 = network;
|
|
581
|
+
if (!(caip2 in NETWORK_ALIASES)) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES).join(", ")}`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
chainId = parseInt(network.split(":")[1]);
|
|
587
|
+
} else {
|
|
588
|
+
const caip2 = NETWORK_ALIASES_V1_TO_V2[network];
|
|
589
|
+
if (!caip2) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
`Unknown network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES_V1_TO_V2).join(", ")}`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
chainId = parseInt(caip2.split(":")[1]);
|
|
595
|
+
}
|
|
579
596
|
if (customChains[chainId]) {
|
|
580
597
|
return customChains[chainId];
|
|
581
598
|
}
|
|
582
599
|
const chain = Object.values(allChains).find((c) => c.id === chainId);
|
|
583
600
|
if (!chain) {
|
|
584
601
|
throw new Error(
|
|
585
|
-
`Unsupported
|
|
602
|
+
`Unsupported chain ID: ${chainId}. Please add custom chain definition in chains.ts`
|
|
586
603
|
);
|
|
587
604
|
}
|
|
588
605
|
return chain;
|
|
@@ -642,15 +659,15 @@ var TransferHook;
|
|
|
642
659
|
);
|
|
643
660
|
}
|
|
644
661
|
TransferHook2.encode = encode;
|
|
645
|
-
function
|
|
662
|
+
function getAddress2(network) {
|
|
646
663
|
const config = getNetworkConfig(network);
|
|
647
664
|
return config.hooks.transfer;
|
|
648
665
|
}
|
|
649
|
-
TransferHook2.getAddress =
|
|
666
|
+
TransferHook2.getAddress = getAddress2;
|
|
650
667
|
})(TransferHook || (TransferHook = {}));
|
|
651
668
|
var NFTMintHook;
|
|
652
669
|
((NFTMintHook2) => {
|
|
653
|
-
function
|
|
670
|
+
function getAddress2(network) {
|
|
654
671
|
const config = getNetworkConfig(network);
|
|
655
672
|
if (!config.demoHooks?.nftMint) {
|
|
656
673
|
throw new Error(
|
|
@@ -659,7 +676,7 @@ var NFTMintHook;
|
|
|
659
676
|
}
|
|
660
677
|
return config.demoHooks.nftMint;
|
|
661
678
|
}
|
|
662
|
-
NFTMintHook2.getAddress =
|
|
679
|
+
NFTMintHook2.getAddress = getAddress2;
|
|
663
680
|
function getNFTContractAddress(network) {
|
|
664
681
|
const config = getNetworkConfig(network);
|
|
665
682
|
if (!config.demoHooks?.randomNFT) {
|
|
@@ -689,7 +706,7 @@ var NFTMintHook;
|
|
|
689
706
|
})(NFTMintHook || (NFTMintHook = {}));
|
|
690
707
|
var RewardHook;
|
|
691
708
|
((RewardHook2) => {
|
|
692
|
-
function
|
|
709
|
+
function getAddress2(network) {
|
|
693
710
|
const config = getNetworkConfig(network);
|
|
694
711
|
if (!config.demoHooks?.reward) {
|
|
695
712
|
throw new Error(
|
|
@@ -698,7 +715,7 @@ var RewardHook;
|
|
|
698
715
|
}
|
|
699
716
|
return config.demoHooks.reward;
|
|
700
717
|
}
|
|
701
|
-
RewardHook2.getAddress =
|
|
718
|
+
RewardHook2.getAddress = getAddress2;
|
|
702
719
|
function getTokenAddress(network) {
|
|
703
720
|
const config = getNetworkConfig(network);
|
|
704
721
|
if (!config.demoHooks?.rewardToken) {
|
|
@@ -800,8 +817,8 @@ function assertValidSettlementExtra(extra) {
|
|
|
800
817
|
|
|
801
818
|
// src/utils.ts
|
|
802
819
|
function addSettlementExtra(requirements, params) {
|
|
803
|
-
const
|
|
804
|
-
const config = getNetworkConfig(
|
|
820
|
+
const networkAlias = getNetworkAlias(requirements.network);
|
|
821
|
+
const config = getNetworkConfig(networkAlias);
|
|
805
822
|
const existingExtra = requirements.extra || {};
|
|
806
823
|
const name = existingExtra.name || config.defaultAsset.eip712.name;
|
|
807
824
|
const version = existingExtra.version || config.defaultAsset.eip712.version;
|
|
@@ -837,6 +854,9 @@ function createRouterSettlementExtension(params) {
|
|
|
837
854
|
if (params?.description !== void 0) {
|
|
838
855
|
info.description = params.description;
|
|
839
856
|
}
|
|
857
|
+
if (params?.salt) {
|
|
858
|
+
info.salt = params.salt;
|
|
859
|
+
}
|
|
840
860
|
if (params?.settlementRouter) info.settlementRouter = params.settlementRouter;
|
|
841
861
|
if (params?.hook) info.hook = params.hook;
|
|
842
862
|
if (params?.hookData) info.hookData = params.hookData;
|
|
@@ -856,8 +876,9 @@ function createRouterSettlementExtension(params) {
|
|
|
856
876
|
finalPayTo: { type: "string", pattern: "^0x[a-fA-F0-9]{40}$" },
|
|
857
877
|
facilitatorFee: { type: "string" }
|
|
858
878
|
},
|
|
859
|
-
// Salt is required in the final enriched version
|
|
860
|
-
|
|
879
|
+
// Salt is required in the final enriched version
|
|
880
|
+
// facilitatorFee is optional (facilitator will calculate if missing)
|
|
881
|
+
required: ["schemaVersion", "salt", "settlementRouter", "hook", "hookData", "finalPayTo"]
|
|
861
882
|
};
|
|
862
883
|
}
|
|
863
884
|
return {
|
|
@@ -898,202 +919,6 @@ function createExtensionDeclaration(params) {
|
|
|
898
919
|
};
|
|
899
920
|
}
|
|
900
921
|
|
|
901
|
-
// src/settlement-routes.ts
|
|
902
|
-
function createSettlementRouteConfig(baseConfig, settlementOptions) {
|
|
903
|
-
const acceptsArray = Array.isArray(baseConfig.accepts) ? baseConfig.accepts : [baseConfig.accepts];
|
|
904
|
-
const firstOption = acceptsArray[0];
|
|
905
|
-
const firstNetwork = firstOption.network;
|
|
906
|
-
const networkConfig = getNetworkConfig(firstNetwork);
|
|
907
|
-
if (!networkConfig) {
|
|
908
|
-
throw new Error(`Network configuration not found for: ${firstNetwork}`);
|
|
909
|
-
}
|
|
910
|
-
const hook = settlementOptions.hook || TransferHook.getAddress(firstNetwork);
|
|
911
|
-
const hookData = settlementOptions.hookData || TransferHook.encode();
|
|
912
|
-
const enhancedAccepts = acceptsArray.map((option) => {
|
|
913
|
-
const network = typeof option.network === "string" ? option.network : option.network;
|
|
914
|
-
const optionNetworkConfig = getNetworkConfig(network);
|
|
915
|
-
if (!optionNetworkConfig) {
|
|
916
|
-
throw new Error(`Network configuration not found for: ${network}`);
|
|
917
|
-
}
|
|
918
|
-
const enhancedOption = {
|
|
919
|
-
...option,
|
|
920
|
-
// Override payTo to use settlementRouter as the immediate recipient
|
|
921
|
-
payTo: optionNetworkConfig.settlementRouter,
|
|
922
|
-
// Only include EIP-712 domain info in extra
|
|
923
|
-
extra: {
|
|
924
|
-
...option.extra || {},
|
|
925
|
-
name: optionNetworkConfig.defaultAsset.eip712.name,
|
|
926
|
-
version: optionNetworkConfig.defaultAsset.eip712.version
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
return enhancedOption;
|
|
930
|
-
});
|
|
931
|
-
const extensions = {
|
|
932
|
-
...baseConfig.extensions || {},
|
|
933
|
-
...createExtensionDeclaration({
|
|
934
|
-
description: settlementOptions.description || "Router settlement with atomic fee distribution",
|
|
935
|
-
// Pass settlement parameters to be included in extension info
|
|
936
|
-
settlementRouter: networkConfig.settlementRouter,
|
|
937
|
-
hook,
|
|
938
|
-
hookData,
|
|
939
|
-
finalPayTo: settlementOptions.finalPayTo,
|
|
940
|
-
facilitatorFee: settlementOptions.facilitatorFee || "0"
|
|
941
|
-
})
|
|
942
|
-
};
|
|
943
|
-
return {
|
|
944
|
-
...baseConfig,
|
|
945
|
-
accepts: enhancedAccepts.length === 1 ? enhancedAccepts[0] : enhancedAccepts,
|
|
946
|
-
extensions
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
function registerSettlementHooks(server, config = {}) {
|
|
950
|
-
const {
|
|
951
|
-
enableSaltExtraction = true,
|
|
952
|
-
validateSettlementParams = true
|
|
953
|
-
} = config;
|
|
954
|
-
if (enableSaltExtraction) {
|
|
955
|
-
server.onBeforeVerify(async (context) => {
|
|
956
|
-
const { paymentPayload, requirements } = context;
|
|
957
|
-
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
958
|
-
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
959
|
-
if (extension?.info) {
|
|
960
|
-
if (!requirements.extra) {
|
|
961
|
-
requirements.extra = {};
|
|
962
|
-
}
|
|
963
|
-
const info = extension.info;
|
|
964
|
-
if (info.salt) requirements.extra.salt = info.salt;
|
|
965
|
-
if (info.settlementRouter) requirements.extra.settlementRouter = info.settlementRouter;
|
|
966
|
-
if (info.hook) requirements.extra.hook = info.hook;
|
|
967
|
-
if (info.hookData) requirements.extra.hookData = info.hookData;
|
|
968
|
-
if (info.finalPayTo) requirements.extra.payTo = info.finalPayTo;
|
|
969
|
-
if (info.facilitatorFee !== void 0) requirements.extra.facilitatorFee = info.facilitatorFee;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
return void 0;
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
if (validateSettlementParams) {
|
|
976
|
-
server.onBeforeSettle(async (context) => {
|
|
977
|
-
const { paymentPayload, requirements } = context;
|
|
978
|
-
let settlementParams = {};
|
|
979
|
-
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
980
|
-
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
981
|
-
if (extension?.info) {
|
|
982
|
-
settlementParams = extension.info;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
if (!settlementParams.settlementRouter && requirements.extra) {
|
|
986
|
-
settlementParams = requirements.extra;
|
|
987
|
-
}
|
|
988
|
-
const requiredFields = ["settlementRouter", "hook", "hookData"];
|
|
989
|
-
const payToField = "finalPayTo" in settlementParams ? "finalPayTo" : "payTo";
|
|
990
|
-
const missingFields = requiredFields.filter((field) => !settlementParams[field]);
|
|
991
|
-
if (!settlementParams[payToField]) {
|
|
992
|
-
missingFields.push(payToField);
|
|
993
|
-
}
|
|
994
|
-
if (missingFields.length > 0) {
|
|
995
|
-
return {
|
|
996
|
-
abort: true,
|
|
997
|
-
reason: `Missing settlement parameters: ${missingFields.join(", ")}`
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
return void 0;
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
// src/helpers.ts
|
|
1006
|
-
function registerRouterSettlement2(server) {
|
|
1007
|
-
return registerRouterSettlement(server);
|
|
1008
|
-
}
|
|
1009
|
-
async function createX402xFacilitator(config) {
|
|
1010
|
-
try {
|
|
1011
|
-
const importFn = new Function("specifier", "return import(specifier)");
|
|
1012
|
-
const facilitatorModule = await importFn("@x402x/facilitator-sdk");
|
|
1013
|
-
return facilitatorModule.createRouterSettlementFacilitator(config);
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
throw new Error(
|
|
1016
|
-
"createX402xFacilitator requires @x402x/facilitator-sdk to be installed. Please install it using your package manager."
|
|
1017
|
-
);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
function withRouterSettlement(requirements, options) {
|
|
1021
|
-
if (!requirements.network) {
|
|
1022
|
-
throw new Error("Network is required in payment requirements");
|
|
1023
|
-
}
|
|
1024
|
-
if (!requirements.asset) {
|
|
1025
|
-
throw new Error("Asset is required in payment requirements");
|
|
1026
|
-
}
|
|
1027
|
-
const networkConfig = getNetworkConfig(requirements.network);
|
|
1028
|
-
if (!networkConfig) {
|
|
1029
|
-
throw new Error(`Network configuration not found for network: ${requirements.network}`);
|
|
1030
|
-
}
|
|
1031
|
-
const salt = options.salt || generateSalt();
|
|
1032
|
-
const settlementExtra = {
|
|
1033
|
-
settlementRouter: networkConfig.settlementRouter,
|
|
1034
|
-
salt,
|
|
1035
|
-
payTo: options.payTo,
|
|
1036
|
-
facilitatorFee: options.facilitatorFee,
|
|
1037
|
-
hook: options.hook,
|
|
1038
|
-
hookData: options.hookData,
|
|
1039
|
-
name: options.name || networkConfig.defaultAsset.eip712.name,
|
|
1040
|
-
version: options.version || networkConfig.defaultAsset.eip712.version
|
|
1041
|
-
};
|
|
1042
|
-
const extensionKey = getRouterSettlementExtensionKey();
|
|
1043
|
-
const extensionDeclaration = createRouterSettlementExtension({
|
|
1044
|
-
description: "Router settlement with atomic fee distribution"
|
|
1045
|
-
});
|
|
1046
|
-
const reqWithExtensions = requirements;
|
|
1047
|
-
return {
|
|
1048
|
-
...requirements,
|
|
1049
|
-
extra: {
|
|
1050
|
-
...reqWithExtensions.extra || {},
|
|
1051
|
-
...settlementExtra
|
|
1052
|
-
},
|
|
1053
|
-
extensions: {
|
|
1054
|
-
...reqWithExtensions.extensions || {},
|
|
1055
|
-
[extensionKey]: extensionDeclaration
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
function isRouterSettlement(requirements) {
|
|
1060
|
-
return !!(requirements.extra && "settlementRouter" in requirements.extra);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
// src/amount.ts
|
|
1064
|
-
var AmountError = class extends Error {
|
|
1065
|
-
constructor(message) {
|
|
1066
|
-
super(message);
|
|
1067
|
-
this.name = "AmountError";
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
function parseDefaultAssetAmount(amount, network) {
|
|
1071
|
-
if (amount === null || amount === void 0 || amount === "") {
|
|
1072
|
-
throw new AmountError("Amount is required");
|
|
1073
|
-
}
|
|
1074
|
-
const result = processPriceToAtomicAmount(amount, network);
|
|
1075
|
-
if ("error" in result) {
|
|
1076
|
-
throw new AmountError(`Invalid amount format: ${result.error}`);
|
|
1077
|
-
}
|
|
1078
|
-
return result.amount;
|
|
1079
|
-
}
|
|
1080
|
-
function formatDefaultAssetAmount(amount, network) {
|
|
1081
|
-
const atomicAmount = BigInt(amount);
|
|
1082
|
-
if (atomicAmount < 0n) {
|
|
1083
|
-
throw new AmountError("Amount cannot be negative");
|
|
1084
|
-
}
|
|
1085
|
-
const asset = getDefaultAsset(network);
|
|
1086
|
-
const decimals = asset.decimals;
|
|
1087
|
-
const amountStr = atomicAmount.toString().padStart(decimals + 1, "0");
|
|
1088
|
-
const integerPart = amountStr.slice(0, -decimals) || "0";
|
|
1089
|
-
const decimalPart = amountStr.slice(-decimals);
|
|
1090
|
-
const trimmedDecimal = decimalPart.replace(/0+$/, "");
|
|
1091
|
-
if (trimmedDecimal) {
|
|
1092
|
-
return `${integerPart}.${trimmedDecimal}`;
|
|
1093
|
-
}
|
|
1094
|
-
return integerPart;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
922
|
// src/facilitator.ts
|
|
1098
923
|
function isSettlementMode(paymentRequirements) {
|
|
1099
924
|
return !!paymentRequirements.extra?.settlementRouter;
|
|
@@ -1300,6 +1125,274 @@ async function settle(facilitatorUrl, paymentPayload, paymentRequirements, timeo
|
|
|
1300
1125
|
}
|
|
1301
1126
|
}
|
|
1302
1127
|
|
|
1128
|
+
// src/settlement-routes.ts
|
|
1129
|
+
var DEFAULT_FACILITATOR_URL = "https://facilitator.x402x.dev";
|
|
1130
|
+
function createSettlementRouteConfig(baseConfig, settlementOptions) {
|
|
1131
|
+
const acceptsArray = Array.isArray(baseConfig.accepts) ? baseConfig.accepts : [baseConfig.accepts];
|
|
1132
|
+
const enhancedAccepts = acceptsArray.map((option) => {
|
|
1133
|
+
const network = typeof option.network === "string" ? option.network : option.network;
|
|
1134
|
+
const optionNetworkConfig = getNetworkConfig(network);
|
|
1135
|
+
if (!optionNetworkConfig) {
|
|
1136
|
+
throw new Error(`Network configuration not found for: ${network}`);
|
|
1137
|
+
}
|
|
1138
|
+
const originalPayTo = typeof option.payTo === "string" ? option.payTo : void 0;
|
|
1139
|
+
const finalPayTo = settlementOptions?.finalPayTo || originalPayTo;
|
|
1140
|
+
if (!finalPayTo) {
|
|
1141
|
+
throw new Error(`Cannot determine finalPayTo: neither settlementOptions.finalPayTo nor option.payTo (string) is provided for network ${network}`);
|
|
1142
|
+
}
|
|
1143
|
+
const hook = settlementOptions?.hook || TransferHook.getAddress(network);
|
|
1144
|
+
const hookData = settlementOptions?.hookData || TransferHook.encode();
|
|
1145
|
+
const facilitatorUrl = settlementOptions?.facilitatorUrl || DEFAULT_FACILITATOR_URL;
|
|
1146
|
+
const hasFixedFee = settlementOptions?.facilitatorFee !== void 0;
|
|
1147
|
+
const dynamicPrice = async (context) => {
|
|
1148
|
+
const httpContext = context;
|
|
1149
|
+
const isRetry = !!httpContext.paymentHeader;
|
|
1150
|
+
if (isRetry) {
|
|
1151
|
+
console.log("[x402x-settlement] Retry request detected, replaying accepted");
|
|
1152
|
+
try {
|
|
1153
|
+
const paymentPayload = decodePaymentSignatureHeader(httpContext.paymentHeader);
|
|
1154
|
+
const accepted = paymentPayload.accepted;
|
|
1155
|
+
if (accepted.network === network && accepted.scheme === option.scheme) {
|
|
1156
|
+
console.log("[x402x-settlement] Replaying accepted for network:", network);
|
|
1157
|
+
return {
|
|
1158
|
+
asset: accepted.asset,
|
|
1159
|
+
amount: accepted.amount,
|
|
1160
|
+
extra: accepted.extra
|
|
1161
|
+
};
|
|
1162
|
+
} else {
|
|
1163
|
+
console.warn("[x402x-settlement] Network/scheme mismatch in retry, falling back to probe");
|
|
1164
|
+
}
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
console.error("[x402x-settlement] Failed to decode payment header, falling back to probe:", error);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
console.log("[x402x-settlement] Probe request, generating new salt and querying fee");
|
|
1170
|
+
const basePrice = typeof option.price === "function" ? await option.price(context) : option.price;
|
|
1171
|
+
let moneyPrice;
|
|
1172
|
+
if (typeof basePrice === "object" && basePrice !== null && "asset" in basePrice) {
|
|
1173
|
+
return basePrice;
|
|
1174
|
+
} else {
|
|
1175
|
+
moneyPrice = basePrice;
|
|
1176
|
+
}
|
|
1177
|
+
const amountStr = typeof moneyPrice === "number" ? moneyPrice.toString() : moneyPrice.toString().replace(/[^0-9.]/g, "");
|
|
1178
|
+
const amountFloat = parseFloat(amountStr);
|
|
1179
|
+
if (isNaN(amountFloat)) {
|
|
1180
|
+
throw new Error(`Invalid price format: ${moneyPrice}`);
|
|
1181
|
+
}
|
|
1182
|
+
const { address, decimals, eip712 } = optionNetworkConfig.defaultAsset;
|
|
1183
|
+
const atomicAmount = BigInt(Math.floor(amountFloat * 10 ** decimals)).toString();
|
|
1184
|
+
const salt = generateSalt();
|
|
1185
|
+
let facilitatorFee;
|
|
1186
|
+
if (hasFixedFee) {
|
|
1187
|
+
facilitatorFee = settlementOptions.facilitatorFee;
|
|
1188
|
+
console.log("[x402x-settlement] Using fixed facilitatorFee:", facilitatorFee);
|
|
1189
|
+
} else {
|
|
1190
|
+
console.log("[x402x-settlement] Querying facilitator for fee:", { network, hook, hookData });
|
|
1191
|
+
try {
|
|
1192
|
+
const feeResult = await calculateFacilitatorFee(facilitatorUrl, network, hook, hookData);
|
|
1193
|
+
if (!feeResult.hookAllowed) {
|
|
1194
|
+
throw new Error(`Hook not allowed by facilitator: ${hook} on network ${network}`);
|
|
1195
|
+
}
|
|
1196
|
+
facilitatorFee = feeResult.facilitatorFee;
|
|
1197
|
+
console.log("[x402x-settlement] Got facilitatorFee from facilitator:", facilitatorFee);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
console.error("[x402x-settlement] Failed to query facilitator fee:", error);
|
|
1200
|
+
throw new Error(
|
|
1201
|
+
`Failed to calculate facilitator fee for network ${network}: ${error instanceof Error ? error.message : String(error)}`
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const settlementExtension = createExtensionDeclaration({
|
|
1206
|
+
description: settlementOptions?.description || "Router settlement with atomic fee distribution",
|
|
1207
|
+
settlementRouter: optionNetworkConfig.settlementRouter,
|
|
1208
|
+
hook,
|
|
1209
|
+
hookData,
|
|
1210
|
+
finalPayTo,
|
|
1211
|
+
facilitatorFee,
|
|
1212
|
+
salt
|
|
1213
|
+
});
|
|
1214
|
+
return {
|
|
1215
|
+
asset: address,
|
|
1216
|
+
amount: atomicAmount,
|
|
1217
|
+
extra: {
|
|
1218
|
+
// EIP-712 domain parameters (scheme-specific for signing)
|
|
1219
|
+
name: eip712.name,
|
|
1220
|
+
version: eip712.version,
|
|
1221
|
+
// Network-specific settlement extension parameters (per-option x402x declaration with salt + fee)
|
|
1222
|
+
[ROUTER_SETTLEMENT_KEY]: settlementExtension[ROUTER_SETTLEMENT_KEY]
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
};
|
|
1226
|
+
const enhancedOption = {
|
|
1227
|
+
...option,
|
|
1228
|
+
// Override payTo to use settlementRouter as the immediate recipient
|
|
1229
|
+
payTo: optionNetworkConfig.settlementRouter,
|
|
1230
|
+
// Use DynamicPrice that queries fee on probe and replays on retry
|
|
1231
|
+
price: dynamicPrice,
|
|
1232
|
+
// Keep option.extra for any user-provided context (primary data is now in price.extra via dynamic function)
|
|
1233
|
+
extra: option.extra
|
|
1234
|
+
};
|
|
1235
|
+
return enhancedOption;
|
|
1236
|
+
});
|
|
1237
|
+
const extensions = {
|
|
1238
|
+
...baseConfig.extensions || {}
|
|
1239
|
+
// Only include non-network-specific metadata at root level
|
|
1240
|
+
// Per-option x402x info is already in accepts[i].price.extra[ROUTER_SETTLEMENT_KEY]
|
|
1241
|
+
};
|
|
1242
|
+
return {
|
|
1243
|
+
...baseConfig,
|
|
1244
|
+
accepts: enhancedAccepts.length === 1 ? enhancedAccepts[0] : enhancedAccepts,
|
|
1245
|
+
extensions
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function registerSettlementHooks(server, config = {}) {
|
|
1249
|
+
const {
|
|
1250
|
+
enableSaltExtraction = true,
|
|
1251
|
+
validateSettlementParams = true
|
|
1252
|
+
} = config;
|
|
1253
|
+
if (enableSaltExtraction) {
|
|
1254
|
+
server.onBeforeVerify(async (context) => {
|
|
1255
|
+
const { paymentPayload, requirements } = context;
|
|
1256
|
+
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
1257
|
+
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
1258
|
+
if (extension?.info) {
|
|
1259
|
+
if (!requirements.extra) {
|
|
1260
|
+
requirements.extra = {};
|
|
1261
|
+
}
|
|
1262
|
+
const info = extension.info;
|
|
1263
|
+
if (info.salt) requirements.extra.salt = info.salt;
|
|
1264
|
+
if (info.settlementRouter) requirements.extra.settlementRouter = info.settlementRouter;
|
|
1265
|
+
if (info.hook) requirements.extra.hook = info.hook;
|
|
1266
|
+
if (info.hookData) requirements.extra.hookData = info.hookData;
|
|
1267
|
+
if (info.finalPayTo) requirements.extra.payTo = info.finalPayTo;
|
|
1268
|
+
if (info.facilitatorFee !== void 0) requirements.extra.facilitatorFee = info.facilitatorFee;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return void 0;
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
if (validateSettlementParams) {
|
|
1275
|
+
server.onBeforeSettle(async (context) => {
|
|
1276
|
+
const { paymentPayload, requirements } = context;
|
|
1277
|
+
let settlementParams = {};
|
|
1278
|
+
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
1279
|
+
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
1280
|
+
if (extension?.info) {
|
|
1281
|
+
settlementParams = extension.info;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (!settlementParams.settlementRouter && requirements.extra) {
|
|
1285
|
+
settlementParams = requirements.extra;
|
|
1286
|
+
}
|
|
1287
|
+
const requiredFields = ["settlementRouter", "hook", "hookData"];
|
|
1288
|
+
const payToField = "finalPayTo" in settlementParams ? "finalPayTo" : "payTo";
|
|
1289
|
+
const missingFields = requiredFields.filter((field) => !settlementParams[field]);
|
|
1290
|
+
if (!settlementParams[payToField]) {
|
|
1291
|
+
missingFields.push(payToField);
|
|
1292
|
+
}
|
|
1293
|
+
if (missingFields.length > 0) {
|
|
1294
|
+
return {
|
|
1295
|
+
abort: true,
|
|
1296
|
+
reason: `Missing settlement parameters: ${missingFields.join(", ")}`
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
return void 0;
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/helpers.ts
|
|
1305
|
+
function registerRouterSettlement2(server) {
|
|
1306
|
+
return registerRouterSettlement(server);
|
|
1307
|
+
}
|
|
1308
|
+
async function createX402xFacilitator(config) {
|
|
1309
|
+
try {
|
|
1310
|
+
const importFn = new Function("specifier", "return import(specifier)");
|
|
1311
|
+
const facilitatorModule = await importFn("@x402x/facilitator-sdk");
|
|
1312
|
+
return facilitatorModule.createRouterSettlementFacilitator(config);
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
throw new Error(
|
|
1315
|
+
"createX402xFacilitator requires @x402x/facilitator-sdk to be installed. Please install it using your package manager."
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function withRouterSettlement(requirements, options) {
|
|
1320
|
+
if (!requirements.network) {
|
|
1321
|
+
throw new Error("Network is required in payment requirements");
|
|
1322
|
+
}
|
|
1323
|
+
if (!requirements.asset) {
|
|
1324
|
+
throw new Error("Asset is required in payment requirements");
|
|
1325
|
+
}
|
|
1326
|
+
const networkConfig = getNetworkConfig(requirements.network);
|
|
1327
|
+
if (!networkConfig) {
|
|
1328
|
+
throw new Error(`Network configuration not found for network: ${requirements.network}`);
|
|
1329
|
+
}
|
|
1330
|
+
const salt = options.salt || generateSalt();
|
|
1331
|
+
const settlementExtra = {
|
|
1332
|
+
settlementRouter: networkConfig.settlementRouter,
|
|
1333
|
+
salt,
|
|
1334
|
+
payTo: options.payTo,
|
|
1335
|
+
facilitatorFee: options.facilitatorFee,
|
|
1336
|
+
hook: options.hook,
|
|
1337
|
+
hookData: options.hookData,
|
|
1338
|
+
name: options.name || networkConfig.defaultAsset.eip712.name,
|
|
1339
|
+
version: options.version || networkConfig.defaultAsset.eip712.version
|
|
1340
|
+
};
|
|
1341
|
+
const extensionKey = getRouterSettlementExtensionKey();
|
|
1342
|
+
const extensionDeclaration = createRouterSettlementExtension({
|
|
1343
|
+
description: "Router settlement with atomic fee distribution"
|
|
1344
|
+
});
|
|
1345
|
+
const reqWithExtensions = requirements;
|
|
1346
|
+
return {
|
|
1347
|
+
...requirements,
|
|
1348
|
+
extra: {
|
|
1349
|
+
...reqWithExtensions.extra || {},
|
|
1350
|
+
...settlementExtra
|
|
1351
|
+
},
|
|
1352
|
+
extensions: {
|
|
1353
|
+
...reqWithExtensions.extensions || {},
|
|
1354
|
+
[extensionKey]: extensionDeclaration
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
function isRouterSettlement(requirements) {
|
|
1359
|
+
return !!(requirements.extra && "settlementRouter" in requirements.extra);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// src/amount.ts
|
|
1363
|
+
var AmountError = class extends Error {
|
|
1364
|
+
constructor(message) {
|
|
1365
|
+
super(message);
|
|
1366
|
+
this.name = "AmountError";
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
function parseDefaultAssetAmount(amount, network) {
|
|
1370
|
+
if (amount === null || amount === void 0 || amount === "") {
|
|
1371
|
+
throw new AmountError("Amount is required");
|
|
1372
|
+
}
|
|
1373
|
+
const result = processPriceToAtomicAmount(amount, network);
|
|
1374
|
+
if ("error" in result) {
|
|
1375
|
+
throw new AmountError(`Invalid amount format: ${result.error}`);
|
|
1376
|
+
}
|
|
1377
|
+
return result.amount;
|
|
1378
|
+
}
|
|
1379
|
+
function formatDefaultAssetAmount(amount, network) {
|
|
1380
|
+
const atomicAmount = BigInt(amount);
|
|
1381
|
+
if (atomicAmount < 0n) {
|
|
1382
|
+
throw new AmountError("Amount cannot be negative");
|
|
1383
|
+
}
|
|
1384
|
+
const asset = getDefaultAsset(network);
|
|
1385
|
+
const decimals = asset.decimals;
|
|
1386
|
+
const amountStr = atomicAmount.toString().padStart(decimals + 1, "0");
|
|
1387
|
+
const integerPart = amountStr.slice(0, -decimals) || "0";
|
|
1388
|
+
const decimalPart = amountStr.slice(-decimals);
|
|
1389
|
+
const trimmedDecimal = decimalPart.replace(/0+$/, "");
|
|
1390
|
+
if (trimmedDecimal) {
|
|
1391
|
+
return `${integerPart}.${trimmedDecimal}`;
|
|
1392
|
+
}
|
|
1393
|
+
return integerPart;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1303
1396
|
// src/abi.ts
|
|
1304
1397
|
var SETTLEMENT_ROUTER_ABI = [
|
|
1305
1398
|
{
|
|
@@ -1391,68 +1484,188 @@ var SettlementRouterError = class extends Error {
|
|
|
1391
1484
|
this.name = "SettlementRouterError";
|
|
1392
1485
|
}
|
|
1393
1486
|
};
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
var
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1487
|
+
var authorizationTypes = {
|
|
1488
|
+
TransferWithAuthorization: [
|
|
1489
|
+
{ name: "from", type: "address" },
|
|
1490
|
+
{ name: "to", type: "address" },
|
|
1491
|
+
{ name: "value", type: "uint256" },
|
|
1492
|
+
{ name: "validAfter", type: "uint256" },
|
|
1493
|
+
{ name: "validBefore", type: "uint256" },
|
|
1494
|
+
{ name: "nonce", type: "bytes32" }
|
|
1495
|
+
]
|
|
1496
|
+
};
|
|
1497
|
+
var ExactEvmSchemeWithRouterSettlement = class {
|
|
1498
|
+
/**
|
|
1499
|
+
* Creates a new ExactEvmSchemeWithRouterSettlement instance.
|
|
1500
|
+
*
|
|
1501
|
+
* @param signer - The EVM signer for client operations (viem WalletClient or LocalAccount)
|
|
1502
|
+
*/
|
|
1503
|
+
constructor(signer) {
|
|
1504
|
+
this.signer = signer;
|
|
1505
|
+
this.scheme = "exact";
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Set router-settlement extension data for the next payment payload creation.
|
|
1509
|
+
*
|
|
1510
|
+
* Intended to be called from an `x402Client.onBeforePaymentCreation` hook, which has access
|
|
1511
|
+
* to `paymentRequired.extensions`.
|
|
1512
|
+
*/
|
|
1513
|
+
setRouterSettlementExtensionFromPaymentRequired(ext) {
|
|
1514
|
+
if (!ext) {
|
|
1515
|
+
this.routerSettlementFromPaymentRequired = void 0;
|
|
1516
|
+
return;
|
|
1408
1517
|
}
|
|
1409
|
-
|
|
1518
|
+
this.routerSettlementFromPaymentRequired = ext;
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Creates a payment payload for the Exact scheme with router settlement.
|
|
1522
|
+
*
|
|
1523
|
+
* This method:
|
|
1524
|
+
* 1. Extracts settlement parameters from PaymentRequired.extensions
|
|
1525
|
+
* 2. Calculates a commitment hash binding all parameters
|
|
1526
|
+
* 3. Uses the commitment as the EIP-3009 nonce
|
|
1527
|
+
* 4. Signs with settlementRouter as the 'to' address
|
|
1528
|
+
*
|
|
1529
|
+
* @param x402Version - The x402 protocol version (must be 2)
|
|
1530
|
+
* @param paymentRequirements - The payment requirements from the server
|
|
1531
|
+
* @returns Promise resolving to a payment payload
|
|
1532
|
+
*
|
|
1533
|
+
* @throws Error if x402Version is not 2
|
|
1534
|
+
* @throws Error if x402x-router-settlement extension is missing
|
|
1535
|
+
* @throws Error if required settlement parameters are missing
|
|
1536
|
+
*/
|
|
1537
|
+
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
1538
|
+
if (x402Version !== 2) {
|
|
1539
|
+
throw new Error(
|
|
1540
|
+
`ExactEvmSchemeWithRouterSettlement only supports x402 version 2, got: ${x402Version}`
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
const routerSettlement = paymentRequirements.extra?.[ROUTER_SETTLEMENT_KEY] ?? this.routerSettlementFromPaymentRequired;
|
|
1544
|
+
if (!routerSettlement?.info) {
|
|
1545
|
+
throw new Error(
|
|
1546
|
+
"x402x-router-settlement extension not available for scheme signing. Ensure the resource server includes the extension in PaymentRequired.extensions and the client registered x402x via registerX402xScheme() (or injected the handler)."
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
const { salt, settlementRouter, hook, hookData, finalPayTo, facilitatorFee } = routerSettlement.info;
|
|
1550
|
+
this.routerSettlementFromPaymentRequired = void 0;
|
|
1551
|
+
if (!salt) throw new Error("Missing required parameter: salt");
|
|
1552
|
+
if (!settlementRouter) throw new Error("Missing required parameter: settlementRouter");
|
|
1553
|
+
if (!hook) throw new Error("Missing required parameter: hook");
|
|
1554
|
+
if (hookData === void 0) throw new Error("Missing required parameter: hookData");
|
|
1555
|
+
if (!finalPayTo) throw new Error("Missing required parameter: finalPayTo");
|
|
1556
|
+
const resolvedFacilitatorFee = facilitatorFee ?? "0";
|
|
1557
|
+
const chainId = parseInt(paymentRequirements.network.split(":")[1]);
|
|
1558
|
+
if (isNaN(chainId)) {
|
|
1559
|
+
throw new Error(`Invalid network format: ${paymentRequirements.network}`);
|
|
1560
|
+
}
|
|
1561
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1562
|
+
const validAfter = (now - 600).toString();
|
|
1563
|
+
const validBefore = (now + paymentRequirements.maxTimeoutSeconds).toString();
|
|
1564
|
+
const commitmentParams = {
|
|
1565
|
+
chainId,
|
|
1566
|
+
hub: settlementRouter,
|
|
1567
|
+
asset: paymentRequirements.asset,
|
|
1568
|
+
from: this.signer.address,
|
|
1569
|
+
value: paymentRequirements.amount,
|
|
1570
|
+
validAfter,
|
|
1571
|
+
validBefore,
|
|
1572
|
+
salt,
|
|
1573
|
+
payTo: finalPayTo,
|
|
1574
|
+
facilitatorFee: resolvedFacilitatorFee,
|
|
1575
|
+
hook,
|
|
1576
|
+
hookData
|
|
1577
|
+
};
|
|
1578
|
+
const nonce = calculateCommitment(commitmentParams);
|
|
1579
|
+
const authorization = {
|
|
1580
|
+
from: this.signer.address,
|
|
1581
|
+
to: getAddress(settlementRouter),
|
|
1582
|
+
value: paymentRequirements.amount,
|
|
1583
|
+
validAfter,
|
|
1584
|
+
validBefore,
|
|
1585
|
+
nonce
|
|
1586
|
+
};
|
|
1587
|
+
const signature = await this.signAuthorization(authorization, paymentRequirements, chainId);
|
|
1588
|
+
const payload = {
|
|
1589
|
+
authorization,
|
|
1590
|
+
signature
|
|
1591
|
+
};
|
|
1592
|
+
return {
|
|
1593
|
+
x402Version,
|
|
1594
|
+
payload
|
|
1595
|
+
};
|
|
1410
1596
|
}
|
|
1411
|
-
};
|
|
1412
|
-
var settleResponseHeader = "X-Payment-Response";
|
|
1413
|
-
var evm = {
|
|
1414
1597
|
/**
|
|
1415
|
-
*
|
|
1598
|
+
* Sign the EIP-3009 authorization using EIP-712
|
|
1599
|
+
*
|
|
1600
|
+
* @param authorization - The authorization to sign
|
|
1601
|
+
* @param requirements - The payment requirements
|
|
1602
|
+
* @param chainId - The chain ID
|
|
1603
|
+
* @returns Promise resolving to the signature
|
|
1416
1604
|
*/
|
|
1417
|
-
|
|
1418
|
-
if (
|
|
1419
|
-
|
|
1605
|
+
async signAuthorization(authorization, requirements, chainId) {
|
|
1606
|
+
if (!requirements.extra?.name || !requirements.extra?.version) {
|
|
1607
|
+
throw new Error(
|
|
1608
|
+
`EIP-712 domain parameters (name, version) are required in payment requirements for asset ${requirements.asset}`
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
const { name, version } = requirements.extra;
|
|
1612
|
+
const domain = {
|
|
1613
|
+
name,
|
|
1614
|
+
version,
|
|
1615
|
+
chainId,
|
|
1616
|
+
verifyingContract: getAddress(requirements.asset)
|
|
1617
|
+
};
|
|
1618
|
+
const message = {
|
|
1619
|
+
from: getAddress(authorization.from),
|
|
1620
|
+
to: getAddress(authorization.to),
|
|
1621
|
+
value: BigInt(authorization.value),
|
|
1622
|
+
validAfter: BigInt(authorization.validAfter),
|
|
1623
|
+
validBefore: BigInt(authorization.validBefore),
|
|
1624
|
+
nonce: authorization.nonce
|
|
1625
|
+
};
|
|
1626
|
+
return await this.signer.signTypedData({
|
|
1627
|
+
domain,
|
|
1628
|
+
types: authorizationTypes,
|
|
1629
|
+
primaryType: "TransferWithAuthorization",
|
|
1630
|
+
message
|
|
1631
|
+
});
|
|
1420
1632
|
}
|
|
1421
1633
|
};
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
};
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
throw new Error("decodeXPaymentResponse is not implemented in v2 - use x402HTTPClient instead");
|
|
1448
|
-
}
|
|
1449
|
-
function useFacilitator(_config) {
|
|
1450
|
-
throw new Error("useFacilitator is not implemented in v2 - use FacilitatorClient instead");
|
|
1634
|
+
|
|
1635
|
+
// src/client/extension-handler.ts
|
|
1636
|
+
function injectX402xExtensionHandler(client, onRouterSettlementExtension) {
|
|
1637
|
+
return client.onBeforePaymentCreation(async (context) => {
|
|
1638
|
+
const { paymentRequired, selectedRequirements } = context;
|
|
1639
|
+
console.log("[x402x-handler] onBeforePaymentCreation called");
|
|
1640
|
+
console.log("[x402x-handler] selectedRequirements.network:", selectedRequirements.network);
|
|
1641
|
+
console.log("[x402x-handler] selectedRequirements.extra keys:", Object.keys(selectedRequirements.extra || {}));
|
|
1642
|
+
const perOptionExtension = selectedRequirements.extra?.[ROUTER_SETTLEMENT_KEY];
|
|
1643
|
+
if (perOptionExtension) {
|
|
1644
|
+
if (!paymentRequired.extensions) {
|
|
1645
|
+
paymentRequired.extensions = {};
|
|
1646
|
+
}
|
|
1647
|
+
paymentRequired.extensions[ROUTER_SETTLEMENT_KEY] = perOptionExtension;
|
|
1648
|
+
console.log("[x402x-handler] \u2705 Copied per-option x402x info into PaymentRequired.extensions");
|
|
1649
|
+
console.log("[x402x-handler] Extension info:", JSON.stringify(perOptionExtension, null, 2));
|
|
1650
|
+
} else {
|
|
1651
|
+
console.warn("[x402x-handler] \u26A0\uFE0F No per-option x402x info found in selectedRequirements.extra");
|
|
1652
|
+
console.warn("[x402x-handler] This may cause facilitator errors. Check server-side createSettlementRouteConfig.");
|
|
1653
|
+
}
|
|
1654
|
+
if (onRouterSettlementExtension) {
|
|
1655
|
+
const extensionToUse = perOptionExtension || paymentRequired.extensions?.[ROUTER_SETTLEMENT_KEY];
|
|
1656
|
+
onRouterSettlementExtension(extensionToUse);
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1451
1659
|
}
|
|
1452
|
-
function
|
|
1453
|
-
|
|
1660
|
+
function registerX402xScheme(client, network, signer) {
|
|
1661
|
+
const scheme = new ExactEvmSchemeWithRouterSettlement(signer);
|
|
1662
|
+
injectX402xExtensionHandler(client, (ext) => {
|
|
1663
|
+
scheme.setRouterSettlementExtensionFromPaymentRequired(ext);
|
|
1664
|
+
});
|
|
1665
|
+
client.register(network, scheme);
|
|
1666
|
+
return client;
|
|
1454
1667
|
}
|
|
1455
1668
|
|
|
1456
|
-
export { AmountError,
|
|
1669
|
+
export { AmountError, ExactEvmSchemeWithRouterSettlement, FacilitatorValidationError, NETWORK_ALIASES, NETWORK_ALIASES_V1_TO_V2, NFTMintHook, ROUTER_SETTLEMENT_KEY, RewardHook, SETTLEMENT_ROUTER_ABI, SettlementExtraError, SettlementRouterError, TransferHook, addSettlementExtra, assertValidSettlementExtra, calculateCommitment, calculateFacilitatorFee, clearFeeCache, computeRoutePatterns, createExtensionDeclaration, createRouterSettlementExtension, createSettlementRouteConfig, createX402xFacilitator, findMatchingPaymentRequirements, findMatchingRoute, formatDefaultAssetAmount, generateSalt, getChain, getChainById, getCustomChains, getDefaultAsset, getNetworkAlias, getNetworkAliasesV1ToV2, getNetworkConfig, getRouterSettlementExtensionKey, getSupportedNetworkAliases, getSupportedNetworkIds, getSupportedNetworks, injectX402xExtensionHandler, isCustomChain, isNetworkSupported, isRouterSettlement, isSettlementMode, isValid32ByteHex, isValidAddress2 as isValidAddress, isValidHex2 as isValidHex, isValidNumericString, networks, parseDefaultAssetAmount, parseSettlementExtra, processPriceToAtomicAmount, registerRouterSettlement2 as registerRouterSettlement, registerSettlementHooks, registerX402xScheme, routerSettlementServerExtension, settle, toCanonicalNetworkKey, toJsonSafe, validateCommitmentParams, validateSettlementExtra, verify, withRouterSettlement };
|
|
1457
1670
|
//# sourceMappingURL=index.mjs.map
|
|
1458
1671
|
//# sourceMappingURL=index.mjs.map
|