@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.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var viem = require('viem');
|
|
4
4
|
var allChains = require('viem/chains');
|
|
5
|
+
var http = require('@x402/core/http');
|
|
5
6
|
|
|
6
7
|
function _interopNamespace(e) {
|
|
7
8
|
if (e && e.__esModule) return e;
|
|
@@ -197,7 +198,8 @@ function isValidHex(hex) {
|
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
// src/network-utils.ts
|
|
200
|
-
var
|
|
201
|
+
var NETWORK_ALIASES_V1_TO_V2 = {
|
|
202
|
+
// V1 human-readable names -> V2 CAIP-2 canonical keys
|
|
201
203
|
"base-sepolia": "eip155:84532",
|
|
202
204
|
"x-layer-testnet": "eip155:1952",
|
|
203
205
|
"skale-base-sepolia": "eip155:324705682",
|
|
@@ -206,15 +208,12 @@ var NETWORK_IDS = {
|
|
|
206
208
|
"bsc-testnet": "eip155:97",
|
|
207
209
|
"bsc": "eip155:56"
|
|
208
210
|
};
|
|
209
|
-
var
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
"eip155:97": "bsc-testnet",
|
|
216
|
-
"eip155:56": "bsc"
|
|
217
|
-
};
|
|
211
|
+
var NETWORK_ALIASES = Object.entries(
|
|
212
|
+
NETWORK_ALIASES_V1_TO_V2
|
|
213
|
+
).reduce((acc, [name, caip2]) => {
|
|
214
|
+
acc[caip2] = name;
|
|
215
|
+
return acc;
|
|
216
|
+
}, {});
|
|
218
217
|
var DEFAULT_ASSETS = {
|
|
219
218
|
"eip155:84532": {
|
|
220
219
|
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
@@ -273,23 +272,14 @@ var DEFAULT_ASSETS = {
|
|
|
273
272
|
}
|
|
274
273
|
}
|
|
275
274
|
};
|
|
276
|
-
function
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
279
|
-
throw new Error(
|
|
280
|
-
`Unsupported network: ${networkName}. Supported networks: ${Object.keys(NETWORK_IDS).join(", ")}`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
return networkId;
|
|
284
|
-
}
|
|
285
|
-
function getNetworkName(network) {
|
|
286
|
-
const networkName = NETWORK_NAMES[network];
|
|
287
|
-
if (!networkName) {
|
|
275
|
+
function getNetworkAlias(network) {
|
|
276
|
+
const networkAlias = NETWORK_ALIASES[network];
|
|
277
|
+
if (!networkAlias) {
|
|
288
278
|
throw new Error(
|
|
289
|
-
`Unsupported network ID: ${network}. Supported network IDs: ${Object.keys(
|
|
279
|
+
`Unsupported network ID: ${network}. Supported network IDs: ${Object.keys(NETWORK_ALIASES).join(", ")}`
|
|
290
280
|
);
|
|
291
281
|
}
|
|
292
|
-
return
|
|
282
|
+
return networkAlias;
|
|
293
283
|
}
|
|
294
284
|
function getDefaultAsset(network) {
|
|
295
285
|
const assetInfo = DEFAULT_ASSETS[network];
|
|
@@ -324,21 +314,8 @@ function processPriceToAtomicAmount(price, network) {
|
|
|
324
314
|
};
|
|
325
315
|
}
|
|
326
316
|
}
|
|
327
|
-
var NETWORK_ALIASES_V1_TO_V2 = {
|
|
328
|
-
// V1 human-readable names -> V2 CAIP-2 canonical keys
|
|
329
|
-
"base-sepolia": "eip155:84532",
|
|
330
|
-
"x-layer-testnet": "eip155:1952",
|
|
331
|
-
"skale-base-sepolia": "eip155:324705682",
|
|
332
|
-
"base": "eip155:8453",
|
|
333
|
-
"x-layer": "eip155:196",
|
|
334
|
-
"bsc-testnet": "eip155:97",
|
|
335
|
-
"bsc": "eip155:56"
|
|
336
|
-
};
|
|
337
317
|
function getSupportedNetworkIds() {
|
|
338
|
-
return Object.keys(
|
|
339
|
-
}
|
|
340
|
-
function getSupportedNetworksV2() {
|
|
341
|
-
return getSupportedNetworkIds();
|
|
318
|
+
return Object.keys(NETWORK_ALIASES);
|
|
342
319
|
}
|
|
343
320
|
function getNetworkAliasesV1ToV2() {
|
|
344
321
|
return { ...NETWORK_ALIASES_V1_TO_V2 };
|
|
@@ -346,11 +323,11 @@ function getNetworkAliasesV1ToV2() {
|
|
|
346
323
|
function toCanonicalNetworkKey(network) {
|
|
347
324
|
if (network.startsWith("eip155:")) {
|
|
348
325
|
const canonicalNetwork2 = network;
|
|
349
|
-
if (canonicalNetwork2 in
|
|
326
|
+
if (canonicalNetwork2 in NETWORK_ALIASES) {
|
|
350
327
|
return canonicalNetwork2;
|
|
351
328
|
}
|
|
352
329
|
throw new Error(
|
|
353
|
-
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(
|
|
330
|
+
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES).join(", ")}`
|
|
354
331
|
);
|
|
355
332
|
}
|
|
356
333
|
const canonicalNetwork = NETWORK_ALIASES_V1_TO_V2[network];
|
|
@@ -374,15 +351,33 @@ function getDefaultAssetConfig(network) {
|
|
|
374
351
|
}
|
|
375
352
|
};
|
|
376
353
|
}
|
|
354
|
+
function normalizeToCAIP2(network) {
|
|
355
|
+
if (network.startsWith("eip155:")) {
|
|
356
|
+
const caip22 = network;
|
|
357
|
+
if (!(caip22 in networks)) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(networks).join(", ")}`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return caip22;
|
|
363
|
+
}
|
|
364
|
+
const caip2 = NETWORK_ALIASES_V1_TO_V2[network];
|
|
365
|
+
if (!caip2) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Unknown network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES_V1_TO_V2).join(", ")}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return caip2;
|
|
371
|
+
}
|
|
377
372
|
var networks = {
|
|
378
|
-
"
|
|
379
|
-
chainId:
|
|
373
|
+
"eip155:84532": {
|
|
374
|
+
chainId: 84532,
|
|
380
375
|
name: "Base Sepolia",
|
|
381
376
|
type: "testnet",
|
|
382
377
|
addressExplorerBaseUrl: "https://sepolia.basescan.org/address/",
|
|
383
378
|
txExplorerBaseUrl: "https://sepolia.basescan.org/tx/",
|
|
384
379
|
settlementRouter: "0x817e4f0ee2fbdaac426f1178e149f7dc98873ecb",
|
|
385
|
-
defaultAsset: getDefaultAssetConfig(
|
|
380
|
+
defaultAsset: getDefaultAssetConfig("eip155:84532"),
|
|
386
381
|
hooks: {
|
|
387
382
|
transfer: "0x4DE234059C6CcC94B8fE1eb1BD24804794083569"
|
|
388
383
|
},
|
|
@@ -397,14 +392,14 @@ var networks = {
|
|
|
397
392
|
nativeToken: "ETH"
|
|
398
393
|
}
|
|
399
394
|
},
|
|
400
|
-
"
|
|
401
|
-
chainId:
|
|
395
|
+
"eip155:1952": {
|
|
396
|
+
chainId: 1952,
|
|
402
397
|
name: "X Layer Testnet",
|
|
403
398
|
type: "testnet",
|
|
404
399
|
addressExplorerBaseUrl: "https://www.oklink.com/xlayer-test/address/",
|
|
405
400
|
txExplorerBaseUrl: "https://www.oklink.com/xlayer-test/tx/",
|
|
406
401
|
settlementRouter: "0xba9980fb08771e2fd10c17450f52d39bcb9ed576",
|
|
407
|
-
defaultAsset: getDefaultAssetConfig(
|
|
402
|
+
defaultAsset: getDefaultAssetConfig("eip155:1952"),
|
|
408
403
|
hooks: {
|
|
409
404
|
transfer: "0xD4b98dd614c1Ea472fC4547a5d2B93f3D3637BEE"
|
|
410
405
|
},
|
|
@@ -419,14 +414,14 @@ var networks = {
|
|
|
419
414
|
nativeToken: "OKB"
|
|
420
415
|
}
|
|
421
416
|
},
|
|
422
|
-
"
|
|
423
|
-
chainId:
|
|
417
|
+
"eip155:324705682": {
|
|
418
|
+
chainId: 324705682,
|
|
424
419
|
name: "SKALE Base Sepolia",
|
|
425
420
|
type: "testnet",
|
|
426
421
|
addressExplorerBaseUrl: "https://base-sepolia-testnet-explorer.skalenodes.com/address/",
|
|
427
422
|
txExplorerBaseUrl: "https://base-sepolia-testnet-explorer.skalenodes.com/tx/",
|
|
428
423
|
settlementRouter: "0x1Ae0E196dC18355aF3a19985faf67354213F833D",
|
|
429
|
-
defaultAsset: getDefaultAssetConfig(
|
|
424
|
+
defaultAsset: getDefaultAssetConfig("eip155:324705682"),
|
|
430
425
|
hooks: {
|
|
431
426
|
transfer: "0x2f05fe5674aE756E25C26855258B4877E9e021Fd"
|
|
432
427
|
},
|
|
@@ -441,14 +436,14 @@ var networks = {
|
|
|
441
436
|
nativeToken: "Credits"
|
|
442
437
|
}
|
|
443
438
|
},
|
|
444
|
-
"
|
|
445
|
-
chainId:
|
|
439
|
+
"eip155:97": {
|
|
440
|
+
chainId: 97,
|
|
446
441
|
name: "BSC Testnet",
|
|
447
442
|
type: "testnet",
|
|
448
443
|
addressExplorerBaseUrl: "https://testnet.bscscan.com/address/",
|
|
449
444
|
txExplorerBaseUrl: "https://testnet.bscscan.com/tx/",
|
|
450
445
|
settlementRouter: "0x1Ae0E196dC18355aF3a19985faf67354213F833D",
|
|
451
|
-
defaultAsset: getDefaultAssetConfig(
|
|
446
|
+
defaultAsset: getDefaultAssetConfig("eip155:97"),
|
|
452
447
|
hooks: {
|
|
453
448
|
transfer: "0x2f05fe5674aE756E25C26855258B4877E9e021Fd"
|
|
454
449
|
},
|
|
@@ -464,14 +459,14 @@ var networks = {
|
|
|
464
459
|
}
|
|
465
460
|
},
|
|
466
461
|
// Mainnet configurations
|
|
467
|
-
|
|
468
|
-
chainId:
|
|
462
|
+
"eip155:8453": {
|
|
463
|
+
chainId: 8453,
|
|
469
464
|
name: "Base Mainnet",
|
|
470
465
|
type: "mainnet",
|
|
471
466
|
addressExplorerBaseUrl: "https://basescan.org/address/",
|
|
472
467
|
txExplorerBaseUrl: "https://basescan.org/tx/",
|
|
473
468
|
settlementRouter: "0x73fc659Cd5494E69852bE8D9D23FE05Aab14b29B",
|
|
474
|
-
defaultAsset: getDefaultAssetConfig(
|
|
469
|
+
defaultAsset: getDefaultAssetConfig("eip155:8453"),
|
|
475
470
|
hooks: {
|
|
476
471
|
transfer: "0x081258287F692D61575387ee2a4075f34dd7Aef7"
|
|
477
472
|
},
|
|
@@ -486,14 +481,14 @@ var networks = {
|
|
|
486
481
|
nativeToken: "ETH"
|
|
487
482
|
}
|
|
488
483
|
},
|
|
489
|
-
"
|
|
490
|
-
chainId:
|
|
484
|
+
"eip155:196": {
|
|
485
|
+
chainId: 196,
|
|
491
486
|
name: "X Layer Mainnet",
|
|
492
487
|
type: "mainnet",
|
|
493
488
|
addressExplorerBaseUrl: "https://www.oklink.com/xlayer/address/",
|
|
494
489
|
txExplorerBaseUrl: "https://www.oklink.com/xlayer/tx/",
|
|
495
490
|
settlementRouter: "0x73fc659Cd5494E69852bE8D9D23FE05Aab14b29B",
|
|
496
|
-
defaultAsset: getDefaultAssetConfig(
|
|
491
|
+
defaultAsset: getDefaultAssetConfig("eip155:196"),
|
|
497
492
|
hooks: {
|
|
498
493
|
transfer: "0x081258287F692D61575387ee2a4075f34dd7Aef7"
|
|
499
494
|
},
|
|
@@ -508,14 +503,14 @@ var networks = {
|
|
|
508
503
|
nativeToken: "OKB"
|
|
509
504
|
}
|
|
510
505
|
},
|
|
511
|
-
|
|
512
|
-
chainId:
|
|
506
|
+
"eip155:56": {
|
|
507
|
+
chainId: 56,
|
|
513
508
|
name: "BSC Mainnet",
|
|
514
509
|
type: "mainnet",
|
|
515
510
|
addressExplorerBaseUrl: "https://bscscan.com/address/",
|
|
516
511
|
txExplorerBaseUrl: "https://bscscan.com/tx/",
|
|
517
512
|
settlementRouter: "0x1Ae0E196dC18355aF3a19985faf67354213F833D",
|
|
518
|
-
defaultAsset: getDefaultAssetConfig(
|
|
513
|
+
defaultAsset: getDefaultAssetConfig("eip155:56"),
|
|
519
514
|
hooks: {
|
|
520
515
|
transfer: "0x2f05fe5674aE756E25C26855258B4877E9e021Fd"
|
|
521
516
|
},
|
|
@@ -532,7 +527,8 @@ var networks = {
|
|
|
532
527
|
}
|
|
533
528
|
};
|
|
534
529
|
function getNetworkConfig(network) {
|
|
535
|
-
const
|
|
530
|
+
const caip2Network = normalizeToCAIP2(network);
|
|
531
|
+
const config = networks[caip2Network];
|
|
536
532
|
if (!config) {
|
|
537
533
|
throw new Error(
|
|
538
534
|
`Unsupported network: ${network}. Supported networks: ${Object.keys(networks).join(", ")}`
|
|
@@ -541,13 +537,18 @@ function getNetworkConfig(network) {
|
|
|
541
537
|
return config;
|
|
542
538
|
}
|
|
543
539
|
function isNetworkSupported(network) {
|
|
544
|
-
|
|
540
|
+
try {
|
|
541
|
+
const caip2Network = normalizeToCAIP2(network);
|
|
542
|
+
return caip2Network in networks;
|
|
543
|
+
} catch {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
545
546
|
}
|
|
546
|
-
function
|
|
547
|
-
return Object.keys(networks);
|
|
547
|
+
function getSupportedNetworkAliases() {
|
|
548
|
+
return Object.keys(networks).map((caip2) => getNetworkAlias(caip2));
|
|
548
549
|
}
|
|
549
550
|
function getSupportedNetworks() {
|
|
550
|
-
return
|
|
551
|
+
return Object.keys(networks);
|
|
551
552
|
}
|
|
552
553
|
var customChains = {
|
|
553
554
|
// X Layer Testnet
|
|
@@ -596,15 +597,31 @@ var customChains = {
|
|
|
596
597
|
})
|
|
597
598
|
};
|
|
598
599
|
function getChain(network) {
|
|
599
|
-
|
|
600
|
-
|
|
600
|
+
let chainId;
|
|
601
|
+
if (network.startsWith("eip155:")) {
|
|
602
|
+
const caip2 = network;
|
|
603
|
+
if (!(caip2 in NETWORK_ALIASES)) {
|
|
604
|
+
throw new Error(
|
|
605
|
+
`Unsupported CAIP-2 network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES).join(", ")}`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
chainId = parseInt(network.split(":")[1]);
|
|
609
|
+
} else {
|
|
610
|
+
const caip2 = NETWORK_ALIASES_V1_TO_V2[network];
|
|
611
|
+
if (!caip2) {
|
|
612
|
+
throw new Error(
|
|
613
|
+
`Unknown network: ${network}. Supported networks: ${Object.keys(NETWORK_ALIASES_V1_TO_V2).join(", ")}`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
chainId = parseInt(caip2.split(":")[1]);
|
|
617
|
+
}
|
|
601
618
|
if (customChains[chainId]) {
|
|
602
619
|
return customChains[chainId];
|
|
603
620
|
}
|
|
604
621
|
const chain = Object.values(allChains__namespace).find((c) => c.id === chainId);
|
|
605
622
|
if (!chain) {
|
|
606
623
|
throw new Error(
|
|
607
|
-
`Unsupported
|
|
624
|
+
`Unsupported chain ID: ${chainId}. Please add custom chain definition in chains.ts`
|
|
608
625
|
);
|
|
609
626
|
}
|
|
610
627
|
return chain;
|
|
@@ -664,15 +681,15 @@ exports.TransferHook = void 0;
|
|
|
664
681
|
);
|
|
665
682
|
}
|
|
666
683
|
TransferHook2.encode = encode;
|
|
667
|
-
function
|
|
684
|
+
function getAddress2(network) {
|
|
668
685
|
const config = getNetworkConfig(network);
|
|
669
686
|
return config.hooks.transfer;
|
|
670
687
|
}
|
|
671
|
-
TransferHook2.getAddress =
|
|
688
|
+
TransferHook2.getAddress = getAddress2;
|
|
672
689
|
})(exports.TransferHook || (exports.TransferHook = {}));
|
|
673
690
|
exports.NFTMintHook = void 0;
|
|
674
691
|
((NFTMintHook2) => {
|
|
675
|
-
function
|
|
692
|
+
function getAddress2(network) {
|
|
676
693
|
const config = getNetworkConfig(network);
|
|
677
694
|
if (!config.demoHooks?.nftMint) {
|
|
678
695
|
throw new Error(
|
|
@@ -681,7 +698,7 @@ exports.NFTMintHook = void 0;
|
|
|
681
698
|
}
|
|
682
699
|
return config.demoHooks.nftMint;
|
|
683
700
|
}
|
|
684
|
-
NFTMintHook2.getAddress =
|
|
701
|
+
NFTMintHook2.getAddress = getAddress2;
|
|
685
702
|
function getNFTContractAddress(network) {
|
|
686
703
|
const config = getNetworkConfig(network);
|
|
687
704
|
if (!config.demoHooks?.randomNFT) {
|
|
@@ -711,7 +728,7 @@ exports.NFTMintHook = void 0;
|
|
|
711
728
|
})(exports.NFTMintHook || (exports.NFTMintHook = {}));
|
|
712
729
|
exports.RewardHook = void 0;
|
|
713
730
|
((RewardHook2) => {
|
|
714
|
-
function
|
|
731
|
+
function getAddress2(network) {
|
|
715
732
|
const config = getNetworkConfig(network);
|
|
716
733
|
if (!config.demoHooks?.reward) {
|
|
717
734
|
throw new Error(
|
|
@@ -720,7 +737,7 @@ exports.RewardHook = void 0;
|
|
|
720
737
|
}
|
|
721
738
|
return config.demoHooks.reward;
|
|
722
739
|
}
|
|
723
|
-
RewardHook2.getAddress =
|
|
740
|
+
RewardHook2.getAddress = getAddress2;
|
|
724
741
|
function getTokenAddress(network) {
|
|
725
742
|
const config = getNetworkConfig(network);
|
|
726
743
|
if (!config.demoHooks?.rewardToken) {
|
|
@@ -822,8 +839,8 @@ function assertValidSettlementExtra(extra) {
|
|
|
822
839
|
|
|
823
840
|
// src/utils.ts
|
|
824
841
|
function addSettlementExtra(requirements, params) {
|
|
825
|
-
const
|
|
826
|
-
const config = getNetworkConfig(
|
|
842
|
+
const networkAlias = getNetworkAlias(requirements.network);
|
|
843
|
+
const config = getNetworkConfig(networkAlias);
|
|
827
844
|
const existingExtra = requirements.extra || {};
|
|
828
845
|
const name = existingExtra.name || config.defaultAsset.eip712.name;
|
|
829
846
|
const version = existingExtra.version || config.defaultAsset.eip712.version;
|
|
@@ -859,6 +876,9 @@ function createRouterSettlementExtension(params) {
|
|
|
859
876
|
if (params?.description !== void 0) {
|
|
860
877
|
info.description = params.description;
|
|
861
878
|
}
|
|
879
|
+
if (params?.salt) {
|
|
880
|
+
info.salt = params.salt;
|
|
881
|
+
}
|
|
862
882
|
if (params?.settlementRouter) info.settlementRouter = params.settlementRouter;
|
|
863
883
|
if (params?.hook) info.hook = params.hook;
|
|
864
884
|
if (params?.hookData) info.hookData = params.hookData;
|
|
@@ -878,8 +898,9 @@ function createRouterSettlementExtension(params) {
|
|
|
878
898
|
finalPayTo: { type: "string", pattern: "^0x[a-fA-F0-9]{40}$" },
|
|
879
899
|
facilitatorFee: { type: "string" }
|
|
880
900
|
},
|
|
881
|
-
// Salt is required in the final enriched version
|
|
882
|
-
|
|
901
|
+
// Salt is required in the final enriched version
|
|
902
|
+
// facilitatorFee is optional (facilitator will calculate if missing)
|
|
903
|
+
required: ["schemaVersion", "salt", "settlementRouter", "hook", "hookData", "finalPayTo"]
|
|
883
904
|
};
|
|
884
905
|
}
|
|
885
906
|
return {
|
|
@@ -920,202 +941,6 @@ function createExtensionDeclaration(params) {
|
|
|
920
941
|
};
|
|
921
942
|
}
|
|
922
943
|
|
|
923
|
-
// src/settlement-routes.ts
|
|
924
|
-
function createSettlementRouteConfig(baseConfig, settlementOptions) {
|
|
925
|
-
const acceptsArray = Array.isArray(baseConfig.accepts) ? baseConfig.accepts : [baseConfig.accepts];
|
|
926
|
-
const firstOption = acceptsArray[0];
|
|
927
|
-
const firstNetwork = firstOption.network;
|
|
928
|
-
const networkConfig = getNetworkConfig(firstNetwork);
|
|
929
|
-
if (!networkConfig) {
|
|
930
|
-
throw new Error(`Network configuration not found for: ${firstNetwork}`);
|
|
931
|
-
}
|
|
932
|
-
const hook = settlementOptions.hook || exports.TransferHook.getAddress(firstNetwork);
|
|
933
|
-
const hookData = settlementOptions.hookData || exports.TransferHook.encode();
|
|
934
|
-
const enhancedAccepts = acceptsArray.map((option) => {
|
|
935
|
-
const network = typeof option.network === "string" ? option.network : option.network;
|
|
936
|
-
const optionNetworkConfig = getNetworkConfig(network);
|
|
937
|
-
if (!optionNetworkConfig) {
|
|
938
|
-
throw new Error(`Network configuration not found for: ${network}`);
|
|
939
|
-
}
|
|
940
|
-
const enhancedOption = {
|
|
941
|
-
...option,
|
|
942
|
-
// Override payTo to use settlementRouter as the immediate recipient
|
|
943
|
-
payTo: optionNetworkConfig.settlementRouter,
|
|
944
|
-
// Only include EIP-712 domain info in extra
|
|
945
|
-
extra: {
|
|
946
|
-
...option.extra || {},
|
|
947
|
-
name: optionNetworkConfig.defaultAsset.eip712.name,
|
|
948
|
-
version: optionNetworkConfig.defaultAsset.eip712.version
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
return enhancedOption;
|
|
952
|
-
});
|
|
953
|
-
const extensions = {
|
|
954
|
-
...baseConfig.extensions || {},
|
|
955
|
-
...createExtensionDeclaration({
|
|
956
|
-
description: settlementOptions.description || "Router settlement with atomic fee distribution",
|
|
957
|
-
// Pass settlement parameters to be included in extension info
|
|
958
|
-
settlementRouter: networkConfig.settlementRouter,
|
|
959
|
-
hook,
|
|
960
|
-
hookData,
|
|
961
|
-
finalPayTo: settlementOptions.finalPayTo,
|
|
962
|
-
facilitatorFee: settlementOptions.facilitatorFee || "0"
|
|
963
|
-
})
|
|
964
|
-
};
|
|
965
|
-
return {
|
|
966
|
-
...baseConfig,
|
|
967
|
-
accepts: enhancedAccepts.length === 1 ? enhancedAccepts[0] : enhancedAccepts,
|
|
968
|
-
extensions
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
function registerSettlementHooks(server, config = {}) {
|
|
972
|
-
const {
|
|
973
|
-
enableSaltExtraction = true,
|
|
974
|
-
validateSettlementParams = true
|
|
975
|
-
} = config;
|
|
976
|
-
if (enableSaltExtraction) {
|
|
977
|
-
server.onBeforeVerify(async (context) => {
|
|
978
|
-
const { paymentPayload, requirements } = context;
|
|
979
|
-
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
980
|
-
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
981
|
-
if (extension?.info) {
|
|
982
|
-
if (!requirements.extra) {
|
|
983
|
-
requirements.extra = {};
|
|
984
|
-
}
|
|
985
|
-
const info = extension.info;
|
|
986
|
-
if (info.salt) requirements.extra.salt = info.salt;
|
|
987
|
-
if (info.settlementRouter) requirements.extra.settlementRouter = info.settlementRouter;
|
|
988
|
-
if (info.hook) requirements.extra.hook = info.hook;
|
|
989
|
-
if (info.hookData) requirements.extra.hookData = info.hookData;
|
|
990
|
-
if (info.finalPayTo) requirements.extra.payTo = info.finalPayTo;
|
|
991
|
-
if (info.facilitatorFee !== void 0) requirements.extra.facilitatorFee = info.facilitatorFee;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
return void 0;
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
if (validateSettlementParams) {
|
|
998
|
-
server.onBeforeSettle(async (context) => {
|
|
999
|
-
const { paymentPayload, requirements } = context;
|
|
1000
|
-
let settlementParams = {};
|
|
1001
|
-
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
1002
|
-
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
1003
|
-
if (extension?.info) {
|
|
1004
|
-
settlementParams = extension.info;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
if (!settlementParams.settlementRouter && requirements.extra) {
|
|
1008
|
-
settlementParams = requirements.extra;
|
|
1009
|
-
}
|
|
1010
|
-
const requiredFields = ["settlementRouter", "hook", "hookData"];
|
|
1011
|
-
const payToField = "finalPayTo" in settlementParams ? "finalPayTo" : "payTo";
|
|
1012
|
-
const missingFields = requiredFields.filter((field) => !settlementParams[field]);
|
|
1013
|
-
if (!settlementParams[payToField]) {
|
|
1014
|
-
missingFields.push(payToField);
|
|
1015
|
-
}
|
|
1016
|
-
if (missingFields.length > 0) {
|
|
1017
|
-
return {
|
|
1018
|
-
abort: true,
|
|
1019
|
-
reason: `Missing settlement parameters: ${missingFields.join(", ")}`
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
return void 0;
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// src/helpers.ts
|
|
1028
|
-
function registerRouterSettlement2(server) {
|
|
1029
|
-
return registerRouterSettlement(server);
|
|
1030
|
-
}
|
|
1031
|
-
async function createX402xFacilitator(config) {
|
|
1032
|
-
try {
|
|
1033
|
-
const importFn = new Function("specifier", "return import(specifier)");
|
|
1034
|
-
const facilitatorModule = await importFn("@x402x/facilitator-sdk");
|
|
1035
|
-
return facilitatorModule.createRouterSettlementFacilitator(config);
|
|
1036
|
-
} catch (error) {
|
|
1037
|
-
throw new Error(
|
|
1038
|
-
"createX402xFacilitator requires @x402x/facilitator-sdk to be installed. Please install it using your package manager."
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
function withRouterSettlement(requirements, options) {
|
|
1043
|
-
if (!requirements.network) {
|
|
1044
|
-
throw new Error("Network is required in payment requirements");
|
|
1045
|
-
}
|
|
1046
|
-
if (!requirements.asset) {
|
|
1047
|
-
throw new Error("Asset is required in payment requirements");
|
|
1048
|
-
}
|
|
1049
|
-
const networkConfig = getNetworkConfig(requirements.network);
|
|
1050
|
-
if (!networkConfig) {
|
|
1051
|
-
throw new Error(`Network configuration not found for network: ${requirements.network}`);
|
|
1052
|
-
}
|
|
1053
|
-
const salt = options.salt || generateSalt();
|
|
1054
|
-
const settlementExtra = {
|
|
1055
|
-
settlementRouter: networkConfig.settlementRouter,
|
|
1056
|
-
salt,
|
|
1057
|
-
payTo: options.payTo,
|
|
1058
|
-
facilitatorFee: options.facilitatorFee,
|
|
1059
|
-
hook: options.hook,
|
|
1060
|
-
hookData: options.hookData,
|
|
1061
|
-
name: options.name || networkConfig.defaultAsset.eip712.name,
|
|
1062
|
-
version: options.version || networkConfig.defaultAsset.eip712.version
|
|
1063
|
-
};
|
|
1064
|
-
const extensionKey = getRouterSettlementExtensionKey();
|
|
1065
|
-
const extensionDeclaration = createRouterSettlementExtension({
|
|
1066
|
-
description: "Router settlement with atomic fee distribution"
|
|
1067
|
-
});
|
|
1068
|
-
const reqWithExtensions = requirements;
|
|
1069
|
-
return {
|
|
1070
|
-
...requirements,
|
|
1071
|
-
extra: {
|
|
1072
|
-
...reqWithExtensions.extra || {},
|
|
1073
|
-
...settlementExtra
|
|
1074
|
-
},
|
|
1075
|
-
extensions: {
|
|
1076
|
-
...reqWithExtensions.extensions || {},
|
|
1077
|
-
[extensionKey]: extensionDeclaration
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
function isRouterSettlement(requirements) {
|
|
1082
|
-
return !!(requirements.extra && "settlementRouter" in requirements.extra);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// src/amount.ts
|
|
1086
|
-
var AmountError = class extends Error {
|
|
1087
|
-
constructor(message) {
|
|
1088
|
-
super(message);
|
|
1089
|
-
this.name = "AmountError";
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
function parseDefaultAssetAmount(amount, network) {
|
|
1093
|
-
if (amount === null || amount === void 0 || amount === "") {
|
|
1094
|
-
throw new AmountError("Amount is required");
|
|
1095
|
-
}
|
|
1096
|
-
const result = processPriceToAtomicAmount(amount, network);
|
|
1097
|
-
if ("error" in result) {
|
|
1098
|
-
throw new AmountError(`Invalid amount format: ${result.error}`);
|
|
1099
|
-
}
|
|
1100
|
-
return result.amount;
|
|
1101
|
-
}
|
|
1102
|
-
function formatDefaultAssetAmount(amount, network) {
|
|
1103
|
-
const atomicAmount = BigInt(amount);
|
|
1104
|
-
if (atomicAmount < 0n) {
|
|
1105
|
-
throw new AmountError("Amount cannot be negative");
|
|
1106
|
-
}
|
|
1107
|
-
const asset = getDefaultAsset(network);
|
|
1108
|
-
const decimals = asset.decimals;
|
|
1109
|
-
const amountStr = atomicAmount.toString().padStart(decimals + 1, "0");
|
|
1110
|
-
const integerPart = amountStr.slice(0, -decimals) || "0";
|
|
1111
|
-
const decimalPart = amountStr.slice(-decimals);
|
|
1112
|
-
const trimmedDecimal = decimalPart.replace(/0+$/, "");
|
|
1113
|
-
if (trimmedDecimal) {
|
|
1114
|
-
return `${integerPart}.${trimmedDecimal}`;
|
|
1115
|
-
}
|
|
1116
|
-
return integerPart;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
944
|
// src/facilitator.ts
|
|
1120
945
|
function isSettlementMode(paymentRequirements) {
|
|
1121
946
|
return !!paymentRequirements.extra?.settlementRouter;
|
|
@@ -1322,6 +1147,274 @@ async function settle(facilitatorUrl, paymentPayload, paymentRequirements, timeo
|
|
|
1322
1147
|
}
|
|
1323
1148
|
}
|
|
1324
1149
|
|
|
1150
|
+
// src/settlement-routes.ts
|
|
1151
|
+
var DEFAULT_FACILITATOR_URL = "https://facilitator.x402x.dev";
|
|
1152
|
+
function createSettlementRouteConfig(baseConfig, settlementOptions) {
|
|
1153
|
+
const acceptsArray = Array.isArray(baseConfig.accepts) ? baseConfig.accepts : [baseConfig.accepts];
|
|
1154
|
+
const enhancedAccepts = acceptsArray.map((option) => {
|
|
1155
|
+
const network = typeof option.network === "string" ? option.network : option.network;
|
|
1156
|
+
const optionNetworkConfig = getNetworkConfig(network);
|
|
1157
|
+
if (!optionNetworkConfig) {
|
|
1158
|
+
throw new Error(`Network configuration not found for: ${network}`);
|
|
1159
|
+
}
|
|
1160
|
+
const originalPayTo = typeof option.payTo === "string" ? option.payTo : void 0;
|
|
1161
|
+
const finalPayTo = settlementOptions?.finalPayTo || originalPayTo;
|
|
1162
|
+
if (!finalPayTo) {
|
|
1163
|
+
throw new Error(`Cannot determine finalPayTo: neither settlementOptions.finalPayTo nor option.payTo (string) is provided for network ${network}`);
|
|
1164
|
+
}
|
|
1165
|
+
const hook = settlementOptions?.hook || exports.TransferHook.getAddress(network);
|
|
1166
|
+
const hookData = settlementOptions?.hookData || exports.TransferHook.encode();
|
|
1167
|
+
const facilitatorUrl = settlementOptions?.facilitatorUrl || DEFAULT_FACILITATOR_URL;
|
|
1168
|
+
const hasFixedFee = settlementOptions?.facilitatorFee !== void 0;
|
|
1169
|
+
const dynamicPrice = async (context) => {
|
|
1170
|
+
const httpContext = context;
|
|
1171
|
+
const isRetry = !!httpContext.paymentHeader;
|
|
1172
|
+
if (isRetry) {
|
|
1173
|
+
console.log("[x402x-settlement] Retry request detected, replaying accepted");
|
|
1174
|
+
try {
|
|
1175
|
+
const paymentPayload = http.decodePaymentSignatureHeader(httpContext.paymentHeader);
|
|
1176
|
+
const accepted = paymentPayload.accepted;
|
|
1177
|
+
if (accepted.network === network && accepted.scheme === option.scheme) {
|
|
1178
|
+
console.log("[x402x-settlement] Replaying accepted for network:", network);
|
|
1179
|
+
return {
|
|
1180
|
+
asset: accepted.asset,
|
|
1181
|
+
amount: accepted.amount,
|
|
1182
|
+
extra: accepted.extra
|
|
1183
|
+
};
|
|
1184
|
+
} else {
|
|
1185
|
+
console.warn("[x402x-settlement] Network/scheme mismatch in retry, falling back to probe");
|
|
1186
|
+
}
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
console.error("[x402x-settlement] Failed to decode payment header, falling back to probe:", error);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
console.log("[x402x-settlement] Probe request, generating new salt and querying fee");
|
|
1192
|
+
const basePrice = typeof option.price === "function" ? await option.price(context) : option.price;
|
|
1193
|
+
let moneyPrice;
|
|
1194
|
+
if (typeof basePrice === "object" && basePrice !== null && "asset" in basePrice) {
|
|
1195
|
+
return basePrice;
|
|
1196
|
+
} else {
|
|
1197
|
+
moneyPrice = basePrice;
|
|
1198
|
+
}
|
|
1199
|
+
const amountStr = typeof moneyPrice === "number" ? moneyPrice.toString() : moneyPrice.toString().replace(/[^0-9.]/g, "");
|
|
1200
|
+
const amountFloat = parseFloat(amountStr);
|
|
1201
|
+
if (isNaN(amountFloat)) {
|
|
1202
|
+
throw new Error(`Invalid price format: ${moneyPrice}`);
|
|
1203
|
+
}
|
|
1204
|
+
const { address, decimals, eip712 } = optionNetworkConfig.defaultAsset;
|
|
1205
|
+
const atomicAmount = BigInt(Math.floor(amountFloat * 10 ** decimals)).toString();
|
|
1206
|
+
const salt = generateSalt();
|
|
1207
|
+
let facilitatorFee;
|
|
1208
|
+
if (hasFixedFee) {
|
|
1209
|
+
facilitatorFee = settlementOptions.facilitatorFee;
|
|
1210
|
+
console.log("[x402x-settlement] Using fixed facilitatorFee:", facilitatorFee);
|
|
1211
|
+
} else {
|
|
1212
|
+
console.log("[x402x-settlement] Querying facilitator for fee:", { network, hook, hookData });
|
|
1213
|
+
try {
|
|
1214
|
+
const feeResult = await calculateFacilitatorFee(facilitatorUrl, network, hook, hookData);
|
|
1215
|
+
if (!feeResult.hookAllowed) {
|
|
1216
|
+
throw new Error(`Hook not allowed by facilitator: ${hook} on network ${network}`);
|
|
1217
|
+
}
|
|
1218
|
+
facilitatorFee = feeResult.facilitatorFee;
|
|
1219
|
+
console.log("[x402x-settlement] Got facilitatorFee from facilitator:", facilitatorFee);
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
console.error("[x402x-settlement] Failed to query facilitator fee:", error);
|
|
1222
|
+
throw new Error(
|
|
1223
|
+
`Failed to calculate facilitator fee for network ${network}: ${error instanceof Error ? error.message : String(error)}`
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const settlementExtension = createExtensionDeclaration({
|
|
1228
|
+
description: settlementOptions?.description || "Router settlement with atomic fee distribution",
|
|
1229
|
+
settlementRouter: optionNetworkConfig.settlementRouter,
|
|
1230
|
+
hook,
|
|
1231
|
+
hookData,
|
|
1232
|
+
finalPayTo,
|
|
1233
|
+
facilitatorFee,
|
|
1234
|
+
salt
|
|
1235
|
+
});
|
|
1236
|
+
return {
|
|
1237
|
+
asset: address,
|
|
1238
|
+
amount: atomicAmount,
|
|
1239
|
+
extra: {
|
|
1240
|
+
// EIP-712 domain parameters (scheme-specific for signing)
|
|
1241
|
+
name: eip712.name,
|
|
1242
|
+
version: eip712.version,
|
|
1243
|
+
// Network-specific settlement extension parameters (per-option x402x declaration with salt + fee)
|
|
1244
|
+
[ROUTER_SETTLEMENT_KEY]: settlementExtension[ROUTER_SETTLEMENT_KEY]
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
};
|
|
1248
|
+
const enhancedOption = {
|
|
1249
|
+
...option,
|
|
1250
|
+
// Override payTo to use settlementRouter as the immediate recipient
|
|
1251
|
+
payTo: optionNetworkConfig.settlementRouter,
|
|
1252
|
+
// Use DynamicPrice that queries fee on probe and replays on retry
|
|
1253
|
+
price: dynamicPrice,
|
|
1254
|
+
// Keep option.extra for any user-provided context (primary data is now in price.extra via dynamic function)
|
|
1255
|
+
extra: option.extra
|
|
1256
|
+
};
|
|
1257
|
+
return enhancedOption;
|
|
1258
|
+
});
|
|
1259
|
+
const extensions = {
|
|
1260
|
+
...baseConfig.extensions || {}
|
|
1261
|
+
// Only include non-network-specific metadata at root level
|
|
1262
|
+
// Per-option x402x info is already in accepts[i].price.extra[ROUTER_SETTLEMENT_KEY]
|
|
1263
|
+
};
|
|
1264
|
+
return {
|
|
1265
|
+
...baseConfig,
|
|
1266
|
+
accepts: enhancedAccepts.length === 1 ? enhancedAccepts[0] : enhancedAccepts,
|
|
1267
|
+
extensions
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function registerSettlementHooks(server, config = {}) {
|
|
1271
|
+
const {
|
|
1272
|
+
enableSaltExtraction = true,
|
|
1273
|
+
validateSettlementParams = true
|
|
1274
|
+
} = config;
|
|
1275
|
+
if (enableSaltExtraction) {
|
|
1276
|
+
server.onBeforeVerify(async (context) => {
|
|
1277
|
+
const { paymentPayload, requirements } = context;
|
|
1278
|
+
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
1279
|
+
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
1280
|
+
if (extension?.info) {
|
|
1281
|
+
if (!requirements.extra) {
|
|
1282
|
+
requirements.extra = {};
|
|
1283
|
+
}
|
|
1284
|
+
const info = extension.info;
|
|
1285
|
+
if (info.salt) requirements.extra.salt = info.salt;
|
|
1286
|
+
if (info.settlementRouter) requirements.extra.settlementRouter = info.settlementRouter;
|
|
1287
|
+
if (info.hook) requirements.extra.hook = info.hook;
|
|
1288
|
+
if (info.hookData) requirements.extra.hookData = info.hookData;
|
|
1289
|
+
if (info.finalPayTo) requirements.extra.payTo = info.finalPayTo;
|
|
1290
|
+
if (info.facilitatorFee !== void 0) requirements.extra.facilitatorFee = info.facilitatorFee;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return void 0;
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
if (validateSettlementParams) {
|
|
1297
|
+
server.onBeforeSettle(async (context) => {
|
|
1298
|
+
const { paymentPayload, requirements } = context;
|
|
1299
|
+
let settlementParams = {};
|
|
1300
|
+
if (paymentPayload.extensions && "x402x-router-settlement" in paymentPayload.extensions) {
|
|
1301
|
+
const extension = paymentPayload.extensions["x402x-router-settlement"];
|
|
1302
|
+
if (extension?.info) {
|
|
1303
|
+
settlementParams = extension.info;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (!settlementParams.settlementRouter && requirements.extra) {
|
|
1307
|
+
settlementParams = requirements.extra;
|
|
1308
|
+
}
|
|
1309
|
+
const requiredFields = ["settlementRouter", "hook", "hookData"];
|
|
1310
|
+
const payToField = "finalPayTo" in settlementParams ? "finalPayTo" : "payTo";
|
|
1311
|
+
const missingFields = requiredFields.filter((field) => !settlementParams[field]);
|
|
1312
|
+
if (!settlementParams[payToField]) {
|
|
1313
|
+
missingFields.push(payToField);
|
|
1314
|
+
}
|
|
1315
|
+
if (missingFields.length > 0) {
|
|
1316
|
+
return {
|
|
1317
|
+
abort: true,
|
|
1318
|
+
reason: `Missing settlement parameters: ${missingFields.join(", ")}`
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
return void 0;
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/helpers.ts
|
|
1327
|
+
function registerRouterSettlement2(server) {
|
|
1328
|
+
return registerRouterSettlement(server);
|
|
1329
|
+
}
|
|
1330
|
+
async function createX402xFacilitator(config) {
|
|
1331
|
+
try {
|
|
1332
|
+
const importFn = new Function("specifier", "return import(specifier)");
|
|
1333
|
+
const facilitatorModule = await importFn("@x402x/facilitator-sdk");
|
|
1334
|
+
return facilitatorModule.createRouterSettlementFacilitator(config);
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
throw new Error(
|
|
1337
|
+
"createX402xFacilitator requires @x402x/facilitator-sdk to be installed. Please install it using your package manager."
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function withRouterSettlement(requirements, options) {
|
|
1342
|
+
if (!requirements.network) {
|
|
1343
|
+
throw new Error("Network is required in payment requirements");
|
|
1344
|
+
}
|
|
1345
|
+
if (!requirements.asset) {
|
|
1346
|
+
throw new Error("Asset is required in payment requirements");
|
|
1347
|
+
}
|
|
1348
|
+
const networkConfig = getNetworkConfig(requirements.network);
|
|
1349
|
+
if (!networkConfig) {
|
|
1350
|
+
throw new Error(`Network configuration not found for network: ${requirements.network}`);
|
|
1351
|
+
}
|
|
1352
|
+
const salt = options.salt || generateSalt();
|
|
1353
|
+
const settlementExtra = {
|
|
1354
|
+
settlementRouter: networkConfig.settlementRouter,
|
|
1355
|
+
salt,
|
|
1356
|
+
payTo: options.payTo,
|
|
1357
|
+
facilitatorFee: options.facilitatorFee,
|
|
1358
|
+
hook: options.hook,
|
|
1359
|
+
hookData: options.hookData,
|
|
1360
|
+
name: options.name || networkConfig.defaultAsset.eip712.name,
|
|
1361
|
+
version: options.version || networkConfig.defaultAsset.eip712.version
|
|
1362
|
+
};
|
|
1363
|
+
const extensionKey = getRouterSettlementExtensionKey();
|
|
1364
|
+
const extensionDeclaration = createRouterSettlementExtension({
|
|
1365
|
+
description: "Router settlement with atomic fee distribution"
|
|
1366
|
+
});
|
|
1367
|
+
const reqWithExtensions = requirements;
|
|
1368
|
+
return {
|
|
1369
|
+
...requirements,
|
|
1370
|
+
extra: {
|
|
1371
|
+
...reqWithExtensions.extra || {},
|
|
1372
|
+
...settlementExtra
|
|
1373
|
+
},
|
|
1374
|
+
extensions: {
|
|
1375
|
+
...reqWithExtensions.extensions || {},
|
|
1376
|
+
[extensionKey]: extensionDeclaration
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function isRouterSettlement(requirements) {
|
|
1381
|
+
return !!(requirements.extra && "settlementRouter" in requirements.extra);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/amount.ts
|
|
1385
|
+
var AmountError = class extends Error {
|
|
1386
|
+
constructor(message) {
|
|
1387
|
+
super(message);
|
|
1388
|
+
this.name = "AmountError";
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
function parseDefaultAssetAmount(amount, network) {
|
|
1392
|
+
if (amount === null || amount === void 0 || amount === "") {
|
|
1393
|
+
throw new AmountError("Amount is required");
|
|
1394
|
+
}
|
|
1395
|
+
const result = processPriceToAtomicAmount(amount, network);
|
|
1396
|
+
if ("error" in result) {
|
|
1397
|
+
throw new AmountError(`Invalid amount format: ${result.error}`);
|
|
1398
|
+
}
|
|
1399
|
+
return result.amount;
|
|
1400
|
+
}
|
|
1401
|
+
function formatDefaultAssetAmount(amount, network) {
|
|
1402
|
+
const atomicAmount = BigInt(amount);
|
|
1403
|
+
if (atomicAmount < 0n) {
|
|
1404
|
+
throw new AmountError("Amount cannot be negative");
|
|
1405
|
+
}
|
|
1406
|
+
const asset = getDefaultAsset(network);
|
|
1407
|
+
const decimals = asset.decimals;
|
|
1408
|
+
const amountStr = atomicAmount.toString().padStart(decimals + 1, "0");
|
|
1409
|
+
const integerPart = amountStr.slice(0, -decimals) || "0";
|
|
1410
|
+
const decimalPart = amountStr.slice(-decimals);
|
|
1411
|
+
const trimmedDecimal = decimalPart.replace(/0+$/, "");
|
|
1412
|
+
if (trimmedDecimal) {
|
|
1413
|
+
return `${integerPart}.${trimmedDecimal}`;
|
|
1414
|
+
}
|
|
1415
|
+
return integerPart;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1325
1418
|
// src/abi.ts
|
|
1326
1419
|
var SETTLEMENT_ROUTER_ABI = [
|
|
1327
1420
|
{
|
|
@@ -1413,77 +1506,197 @@ var SettlementRouterError = class extends Error {
|
|
|
1413
1506
|
this.name = "SettlementRouterError";
|
|
1414
1507
|
}
|
|
1415
1508
|
};
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
var
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1509
|
+
var authorizationTypes = {
|
|
1510
|
+
TransferWithAuthorization: [
|
|
1511
|
+
{ name: "from", type: "address" },
|
|
1512
|
+
{ name: "to", type: "address" },
|
|
1513
|
+
{ name: "value", type: "uint256" },
|
|
1514
|
+
{ name: "validAfter", type: "uint256" },
|
|
1515
|
+
{ name: "validBefore", type: "uint256" },
|
|
1516
|
+
{ name: "nonce", type: "bytes32" }
|
|
1517
|
+
]
|
|
1518
|
+
};
|
|
1519
|
+
var ExactEvmSchemeWithRouterSettlement = class {
|
|
1520
|
+
/**
|
|
1521
|
+
* Creates a new ExactEvmSchemeWithRouterSettlement instance.
|
|
1522
|
+
*
|
|
1523
|
+
* @param signer - The EVM signer for client operations (viem WalletClient or LocalAccount)
|
|
1524
|
+
*/
|
|
1525
|
+
constructor(signer) {
|
|
1526
|
+
this.signer = signer;
|
|
1527
|
+
this.scheme = "exact";
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Set router-settlement extension data for the next payment payload creation.
|
|
1531
|
+
*
|
|
1532
|
+
* Intended to be called from an `x402Client.onBeforePaymentCreation` hook, which has access
|
|
1533
|
+
* to `paymentRequired.extensions`.
|
|
1534
|
+
*/
|
|
1535
|
+
setRouterSettlementExtensionFromPaymentRequired(ext) {
|
|
1536
|
+
if (!ext) {
|
|
1537
|
+
this.routerSettlementFromPaymentRequired = void 0;
|
|
1538
|
+
return;
|
|
1430
1539
|
}
|
|
1431
|
-
|
|
1540
|
+
this.routerSettlementFromPaymentRequired = ext;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Creates a payment payload for the Exact scheme with router settlement.
|
|
1544
|
+
*
|
|
1545
|
+
* This method:
|
|
1546
|
+
* 1. Extracts settlement parameters from PaymentRequired.extensions
|
|
1547
|
+
* 2. Calculates a commitment hash binding all parameters
|
|
1548
|
+
* 3. Uses the commitment as the EIP-3009 nonce
|
|
1549
|
+
* 4. Signs with settlementRouter as the 'to' address
|
|
1550
|
+
*
|
|
1551
|
+
* @param x402Version - The x402 protocol version (must be 2)
|
|
1552
|
+
* @param paymentRequirements - The payment requirements from the server
|
|
1553
|
+
* @returns Promise resolving to a payment payload
|
|
1554
|
+
*
|
|
1555
|
+
* @throws Error if x402Version is not 2
|
|
1556
|
+
* @throws Error if x402x-router-settlement extension is missing
|
|
1557
|
+
* @throws Error if required settlement parameters are missing
|
|
1558
|
+
*/
|
|
1559
|
+
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
1560
|
+
if (x402Version !== 2) {
|
|
1561
|
+
throw new Error(
|
|
1562
|
+
`ExactEvmSchemeWithRouterSettlement only supports x402 version 2, got: ${x402Version}`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
const routerSettlement = paymentRequirements.extra?.[ROUTER_SETTLEMENT_KEY] ?? this.routerSettlementFromPaymentRequired;
|
|
1566
|
+
if (!routerSettlement?.info) {
|
|
1567
|
+
throw new Error(
|
|
1568
|
+
"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)."
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
const { salt, settlementRouter, hook, hookData, finalPayTo, facilitatorFee } = routerSettlement.info;
|
|
1572
|
+
this.routerSettlementFromPaymentRequired = void 0;
|
|
1573
|
+
if (!salt) throw new Error("Missing required parameter: salt");
|
|
1574
|
+
if (!settlementRouter) throw new Error("Missing required parameter: settlementRouter");
|
|
1575
|
+
if (!hook) throw new Error("Missing required parameter: hook");
|
|
1576
|
+
if (hookData === void 0) throw new Error("Missing required parameter: hookData");
|
|
1577
|
+
if (!finalPayTo) throw new Error("Missing required parameter: finalPayTo");
|
|
1578
|
+
const resolvedFacilitatorFee = facilitatorFee ?? "0";
|
|
1579
|
+
const chainId = parseInt(paymentRequirements.network.split(":")[1]);
|
|
1580
|
+
if (isNaN(chainId)) {
|
|
1581
|
+
throw new Error(`Invalid network format: ${paymentRequirements.network}`);
|
|
1582
|
+
}
|
|
1583
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1584
|
+
const validAfter = (now - 600).toString();
|
|
1585
|
+
const validBefore = (now + paymentRequirements.maxTimeoutSeconds).toString();
|
|
1586
|
+
const commitmentParams = {
|
|
1587
|
+
chainId,
|
|
1588
|
+
hub: settlementRouter,
|
|
1589
|
+
asset: paymentRequirements.asset,
|
|
1590
|
+
from: this.signer.address,
|
|
1591
|
+
value: paymentRequirements.amount,
|
|
1592
|
+
validAfter,
|
|
1593
|
+
validBefore,
|
|
1594
|
+
salt,
|
|
1595
|
+
payTo: finalPayTo,
|
|
1596
|
+
facilitatorFee: resolvedFacilitatorFee,
|
|
1597
|
+
hook,
|
|
1598
|
+
hookData
|
|
1599
|
+
};
|
|
1600
|
+
const nonce = calculateCommitment(commitmentParams);
|
|
1601
|
+
const authorization = {
|
|
1602
|
+
from: this.signer.address,
|
|
1603
|
+
to: viem.getAddress(settlementRouter),
|
|
1604
|
+
value: paymentRequirements.amount,
|
|
1605
|
+
validAfter,
|
|
1606
|
+
validBefore,
|
|
1607
|
+
nonce
|
|
1608
|
+
};
|
|
1609
|
+
const signature = await this.signAuthorization(authorization, paymentRequirements, chainId);
|
|
1610
|
+
const payload = {
|
|
1611
|
+
authorization,
|
|
1612
|
+
signature
|
|
1613
|
+
};
|
|
1614
|
+
return {
|
|
1615
|
+
x402Version,
|
|
1616
|
+
payload
|
|
1617
|
+
};
|
|
1432
1618
|
}
|
|
1433
|
-
};
|
|
1434
|
-
var settleResponseHeader = "X-Payment-Response";
|
|
1435
|
-
var evm = {
|
|
1436
1619
|
/**
|
|
1437
|
-
*
|
|
1620
|
+
* Sign the EIP-3009 authorization using EIP-712
|
|
1621
|
+
*
|
|
1622
|
+
* @param authorization - The authorization to sign
|
|
1623
|
+
* @param requirements - The payment requirements
|
|
1624
|
+
* @param chainId - The chain ID
|
|
1625
|
+
* @returns Promise resolving to the signature
|
|
1438
1626
|
*/
|
|
1439
|
-
|
|
1440
|
-
if (
|
|
1441
|
-
|
|
1627
|
+
async signAuthorization(authorization, requirements, chainId) {
|
|
1628
|
+
if (!requirements.extra?.name || !requirements.extra?.version) {
|
|
1629
|
+
throw new Error(
|
|
1630
|
+
`EIP-712 domain parameters (name, version) are required in payment requirements for asset ${requirements.asset}`
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
const { name, version } = requirements.extra;
|
|
1634
|
+
const domain = {
|
|
1635
|
+
name,
|
|
1636
|
+
version,
|
|
1637
|
+
chainId,
|
|
1638
|
+
verifyingContract: viem.getAddress(requirements.asset)
|
|
1639
|
+
};
|
|
1640
|
+
const message = {
|
|
1641
|
+
from: viem.getAddress(authorization.from),
|
|
1642
|
+
to: viem.getAddress(authorization.to),
|
|
1643
|
+
value: BigInt(authorization.value),
|
|
1644
|
+
validAfter: BigInt(authorization.validAfter),
|
|
1645
|
+
validBefore: BigInt(authorization.validBefore),
|
|
1646
|
+
nonce: authorization.nonce
|
|
1647
|
+
};
|
|
1648
|
+
return await this.signer.signTypedData({
|
|
1649
|
+
domain,
|
|
1650
|
+
types: authorizationTypes,
|
|
1651
|
+
primaryType: "TransferWithAuthorization",
|
|
1652
|
+
message
|
|
1653
|
+
});
|
|
1442
1654
|
}
|
|
1443
1655
|
};
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
};
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
throw new Error("decodeXPaymentResponse is not implemented in v2 - use x402HTTPClient instead");
|
|
1470
|
-
}
|
|
1471
|
-
function useFacilitator(_config) {
|
|
1472
|
-
throw new Error("useFacilitator is not implemented in v2 - use FacilitatorClient instead");
|
|
1656
|
+
|
|
1657
|
+
// src/client/extension-handler.ts
|
|
1658
|
+
function injectX402xExtensionHandler(client, onRouterSettlementExtension) {
|
|
1659
|
+
return client.onBeforePaymentCreation(async (context) => {
|
|
1660
|
+
const { paymentRequired, selectedRequirements } = context;
|
|
1661
|
+
console.log("[x402x-handler] onBeforePaymentCreation called");
|
|
1662
|
+
console.log("[x402x-handler] selectedRequirements.network:", selectedRequirements.network);
|
|
1663
|
+
console.log("[x402x-handler] selectedRequirements.extra keys:", Object.keys(selectedRequirements.extra || {}));
|
|
1664
|
+
const perOptionExtension = selectedRequirements.extra?.[ROUTER_SETTLEMENT_KEY];
|
|
1665
|
+
if (perOptionExtension) {
|
|
1666
|
+
if (!paymentRequired.extensions) {
|
|
1667
|
+
paymentRequired.extensions = {};
|
|
1668
|
+
}
|
|
1669
|
+
paymentRequired.extensions[ROUTER_SETTLEMENT_KEY] = perOptionExtension;
|
|
1670
|
+
console.log("[x402x-handler] \u2705 Copied per-option x402x info into PaymentRequired.extensions");
|
|
1671
|
+
console.log("[x402x-handler] Extension info:", JSON.stringify(perOptionExtension, null, 2));
|
|
1672
|
+
} else {
|
|
1673
|
+
console.warn("[x402x-handler] \u26A0\uFE0F No per-option x402x info found in selectedRequirements.extra");
|
|
1674
|
+
console.warn("[x402x-handler] This may cause facilitator errors. Check server-side createSettlementRouteConfig.");
|
|
1675
|
+
}
|
|
1676
|
+
if (onRouterSettlementExtension) {
|
|
1677
|
+
const extensionToUse = perOptionExtension || paymentRequired.extensions?.[ROUTER_SETTLEMENT_KEY];
|
|
1678
|
+
onRouterSettlementExtension(extensionToUse);
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1473
1681
|
}
|
|
1474
|
-
function
|
|
1475
|
-
|
|
1682
|
+
function registerX402xScheme(client, network, signer) {
|
|
1683
|
+
const scheme = new ExactEvmSchemeWithRouterSettlement(signer);
|
|
1684
|
+
injectX402xExtensionHandler(client, (ext) => {
|
|
1685
|
+
scheme.setRouterSettlementExtensionFromPaymentRequired(ext);
|
|
1686
|
+
});
|
|
1687
|
+
client.register(network, scheme);
|
|
1688
|
+
return client;
|
|
1476
1689
|
}
|
|
1477
1690
|
|
|
1478
1691
|
exports.AmountError = AmountError;
|
|
1479
|
-
exports.
|
|
1692
|
+
exports.ExactEvmSchemeWithRouterSettlement = ExactEvmSchemeWithRouterSettlement;
|
|
1480
1693
|
exports.FacilitatorValidationError = FacilitatorValidationError;
|
|
1694
|
+
exports.NETWORK_ALIASES = NETWORK_ALIASES;
|
|
1481
1695
|
exports.NETWORK_ALIASES_V1_TO_V2 = NETWORK_ALIASES_V1_TO_V2;
|
|
1482
1696
|
exports.ROUTER_SETTLEMENT_KEY = ROUTER_SETTLEMENT_KEY;
|
|
1483
1697
|
exports.SETTLEMENT_ROUTER_ABI = SETTLEMENT_ROUTER_ABI;
|
|
1484
1698
|
exports.SettlementExtraError = SettlementExtraError;
|
|
1485
1699
|
exports.SettlementRouterError = SettlementRouterError;
|
|
1486
|
-
exports.SupportedEVMNetworks = SupportedEVMNetworks;
|
|
1487
1700
|
exports.addSettlementExtra = addSettlementExtra;
|
|
1488
1701
|
exports.assertValidSettlementExtra = assertValidSettlementExtra;
|
|
1489
1702
|
exports.calculateCommitment = calculateCommitment;
|
|
@@ -1491,14 +1704,9 @@ exports.calculateFacilitatorFee = calculateFacilitatorFee;
|
|
|
1491
1704
|
exports.clearFeeCache = clearFeeCache;
|
|
1492
1705
|
exports.computeRoutePatterns = computeRoutePatterns;
|
|
1493
1706
|
exports.createExtensionDeclaration = createExtensionDeclaration;
|
|
1494
|
-
exports.createPaymentHeader = createPaymentHeader;
|
|
1495
1707
|
exports.createRouterSettlementExtension = createRouterSettlementExtension;
|
|
1496
1708
|
exports.createSettlementRouteConfig = createSettlementRouteConfig;
|
|
1497
|
-
exports.createSigner = createSigner;
|
|
1498
1709
|
exports.createX402xFacilitator = createX402xFacilitator;
|
|
1499
|
-
exports.decodeXPaymentResponse = decodeXPaymentResponse;
|
|
1500
|
-
exports.evm = evm;
|
|
1501
|
-
exports.exact = exact;
|
|
1502
1710
|
exports.findMatchingPaymentRequirements = findMatchingPaymentRequirements;
|
|
1503
1711
|
exports.findMatchingRoute = findMatchingRoute;
|
|
1504
1712
|
exports.formatDefaultAssetAmount = formatDefaultAssetAmount;
|
|
@@ -1507,39 +1715,33 @@ exports.getChain = getChain;
|
|
|
1507
1715
|
exports.getChainById = getChainById;
|
|
1508
1716
|
exports.getCustomChains = getCustomChains;
|
|
1509
1717
|
exports.getDefaultAsset = getDefaultAsset;
|
|
1718
|
+
exports.getNetworkAlias = getNetworkAlias;
|
|
1510
1719
|
exports.getNetworkAliasesV1ToV2 = getNetworkAliasesV1ToV2;
|
|
1511
1720
|
exports.getNetworkConfig = getNetworkConfig;
|
|
1512
|
-
exports.getNetworkId = getNetworkId;
|
|
1513
|
-
exports.getNetworkName = getNetworkName;
|
|
1514
1721
|
exports.getRouterSettlementExtensionKey = getRouterSettlementExtensionKey;
|
|
1722
|
+
exports.getSupportedNetworkAliases = getSupportedNetworkAliases;
|
|
1515
1723
|
exports.getSupportedNetworkIds = getSupportedNetworkIds;
|
|
1516
|
-
exports.getSupportedNetworkNames = getSupportedNetworkNames;
|
|
1517
1724
|
exports.getSupportedNetworks = getSupportedNetworks;
|
|
1518
|
-
exports.
|
|
1725
|
+
exports.injectX402xExtensionHandler = injectX402xExtensionHandler;
|
|
1519
1726
|
exports.isCustomChain = isCustomChain;
|
|
1520
|
-
exports.isMultiNetworkSigner = isMultiNetworkSigner;
|
|
1521
1727
|
exports.isNetworkSupported = isNetworkSupported;
|
|
1522
1728
|
exports.isRouterSettlement = isRouterSettlement;
|
|
1523
1729
|
exports.isSettlementMode = isSettlementMode;
|
|
1524
|
-
exports.isSvmSignerWallet = isSvmSignerWallet;
|
|
1525
1730
|
exports.isValid32ByteHex = isValid32ByteHex;
|
|
1526
1731
|
exports.isValidAddress = isValidAddress2;
|
|
1527
1732
|
exports.isValidHex = isValidHex2;
|
|
1528
1733
|
exports.isValidNumericString = isValidNumericString;
|
|
1529
|
-
exports.moneySchema = moneySchema;
|
|
1530
1734
|
exports.networks = networks;
|
|
1531
1735
|
exports.parseDefaultAssetAmount = parseDefaultAssetAmount;
|
|
1532
1736
|
exports.parseSettlementExtra = parseSettlementExtra;
|
|
1533
1737
|
exports.processPriceToAtomicAmount = processPriceToAtomicAmount;
|
|
1534
1738
|
exports.registerRouterSettlement = registerRouterSettlement2;
|
|
1535
1739
|
exports.registerSettlementHooks = registerSettlementHooks;
|
|
1740
|
+
exports.registerX402xScheme = registerX402xScheme;
|
|
1536
1741
|
exports.routerSettlementServerExtension = routerSettlementServerExtension;
|
|
1537
|
-
exports.selectPaymentRequirements = selectPaymentRequirements;
|
|
1538
1742
|
exports.settle = settle;
|
|
1539
|
-
exports.settleResponseHeader = settleResponseHeader;
|
|
1540
1743
|
exports.toCanonicalNetworkKey = toCanonicalNetworkKey;
|
|
1541
1744
|
exports.toJsonSafe = toJsonSafe;
|
|
1542
|
-
exports.useFacilitator = useFacilitator;
|
|
1543
1745
|
exports.validateCommitmentParams = validateCommitmentParams;
|
|
1544
1746
|
exports.validateSettlementExtra = validateSettlementExtra;
|
|
1545
1747
|
exports.verify = verify;
|