@dexterai/x402 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -191,6 +191,7 @@ __export(client_exports, {
191
191
  SOLANA_MAINNET: () => SOLANA_MAINNET,
192
192
  USDC_MINT: () => USDC_MINT,
193
193
  X402Error: () => X402Error,
194
+ capabilitySearch: () => capabilitySearch,
194
195
  createBudgetAccount: () => createBudgetAccount,
195
196
  createEvmAdapter: () => createEvmAdapter,
196
197
  createEvmKeypairWallet: () => createEvmKeypairWallet,
@@ -203,7 +204,6 @@ __export(client_exports, {
203
204
  getSponsoredRecommendations: () => getSponsoredRecommendations,
204
205
  isEvmKeypairWallet: () => isEvmKeypairWallet,
205
206
  isKeypairWallet: () => isKeypairWallet,
206
- searchAPIs: () => searchAPIs,
207
207
  wrapFetch: () => wrapFetch
208
208
  });
209
209
  module.exports = __toCommonJS(client_exports);
@@ -414,16 +414,40 @@ function createSolanaAdapter(config) {
414
414
  }
415
415
 
416
416
  // src/adapters/evm.ts
417
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
418
+ var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
419
+ var PERMIT2_WITNESS_TYPES = {
420
+ PermitWitnessTransferFrom: [
421
+ { name: "permitted", type: "TokenPermissions" },
422
+ { name: "spender", type: "address" },
423
+ { name: "nonce", type: "uint256" },
424
+ { name: "deadline", type: "uint256" },
425
+ { name: "witness", type: "Witness" }
426
+ ],
427
+ TokenPermissions: [
428
+ { name: "token", type: "address" },
429
+ { name: "amount", type: "uint256" }
430
+ ],
431
+ Witness: [
432
+ { name: "to", type: "address" },
433
+ { name: "validAfter", type: "uint256" }
434
+ ]
435
+ };
436
+ var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
417
437
  var BASE_MAINNET = "eip155:8453";
418
438
  var BASE_SEPOLIA = "eip155:84532";
419
439
  var ARBITRUM_ONE = "eip155:42161";
420
440
  var POLYGON = "eip155:137";
421
441
  var OPTIMISM = "eip155:10";
422
442
  var AVALANCHE = "eip155:43114";
443
+ var BSC_MAINNET = "eip155:56";
423
444
  var SKALE_BASE = "eip155:1187947933";
424
445
  var SKALE_BASE_SEPOLIA = "eip155:324705682";
425
446
  var ETHEREUM_MAINNET = "eip155:1";
447
+ var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
448
+ var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
426
449
  var CHAIN_IDS = {
450
+ [BSC_MAINNET]: 56,
427
451
  [BASE_MAINNET]: 8453,
428
452
  [BASE_SEPOLIA]: 84532,
429
453
  [ARBITRUM_ONE]: 42161,
@@ -435,6 +459,7 @@ var CHAIN_IDS = {
435
459
  [ETHEREUM_MAINNET]: 1
436
460
  };
437
461
  var DEFAULT_RPC_URLS2 = {
462
+ [BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
438
463
  [BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
439
464
  [BASE_SEPOLIA]: "https://sepolia.base.org",
440
465
  [ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
@@ -446,6 +471,7 @@ var DEFAULT_RPC_URLS2 = {
446
471
  [ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
447
472
  };
448
473
  var USDC_ADDRESSES = {
474
+ [BSC_MAINNET]: BSC_USDC,
449
475
  [BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
450
476
  [BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
451
477
  [ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
@@ -456,6 +482,10 @@ var USDC_ADDRESSES = {
456
482
  [SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
457
483
  [ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
458
484
  };
485
+ var BSC_STABLECOIN_ADDRESSES = {
486
+ [BSC_USDT]: { symbol: "USDT", decimals: 18 },
487
+ [BSC_USDC]: { symbol: "USDC", decimals: 18 }
488
+ };
459
489
  function isEvmWallet(wallet) {
460
490
  if (!wallet || typeof wallet !== "object") return false;
461
491
  const w = wallet;
@@ -463,7 +493,7 @@ function isEvmWallet(wallet) {
463
493
  }
464
494
  var EvmAdapter = class {
465
495
  name = "EVM";
466
- networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
496
+ networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
467
497
  config;
468
498
  log;
469
499
  constructor(config = {}) {
@@ -474,6 +504,7 @@ var EvmAdapter = class {
474
504
  canHandle(network) {
475
505
  if (this.networks.includes(network)) return true;
476
506
  if (network === "base") return true;
507
+ if (network === "bsc") return true;
477
508
  if (network === "ethereum") return true;
478
509
  if (network === "arbitrum") return true;
479
510
  if (network.startsWith("eip155:")) return true;
@@ -487,6 +518,7 @@ var EvmAdapter = class {
487
518
  return DEFAULT_RPC_URLS2[network];
488
519
  }
489
520
  if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
521
+ if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
490
522
  if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
491
523
  if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
492
524
  return DEFAULT_RPC_URLS2[BASE_MAINNET];
@@ -506,6 +538,7 @@ var EvmAdapter = class {
506
538
  return parseInt(chainIdStr, 10);
507
539
  }
508
540
  if (network === "base") return 8453;
541
+ if (network === "bsc") return 56;
509
542
  if (network === "ethereum") return 1;
510
543
  if (network === "arbitrum") return 42161;
511
544
  return 8453;
@@ -555,13 +588,19 @@ var EvmAdapter = class {
555
588
  const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
556
589
  return selector + paddedAddress;
557
590
  }
558
- async buildTransaction(accept, wallet, _rpcUrl) {
591
+ async buildTransaction(accept, wallet, rpcUrl) {
559
592
  if (!isEvmWallet(wallet)) {
560
593
  throw new Error("Invalid EVM wallet");
561
594
  }
562
595
  if (!wallet.address) {
563
596
  throw new Error("Wallet not connected");
564
597
  }
598
+ if (accept.scheme === "exact-approval") {
599
+ return this.buildApprovalTransaction(accept, wallet, rpcUrl);
600
+ }
601
+ if (accept.extra?.assetTransferMethod === "permit2") {
602
+ return this.buildPermit2Transaction(accept, wallet, rpcUrl);
603
+ }
565
604
  const { payTo, asset, extra } = accept;
566
605
  const amount = accept.amount ?? accept.maxAmountRequired;
567
606
  if (!amount) {
@@ -632,6 +671,393 @@ var EvmAdapter = class {
632
671
  signature
633
672
  };
634
673
  }
674
+ // ===========================================================================
675
+ // exact-approval: BSC and other chains without EIP-3009
676
+ // ===========================================================================
677
+ /**
678
+ * Build a payment transaction for chains that use the approval-based scheme.
679
+ * The facilitator's /supported response provides the EIP-712 domain and types
680
+ * in accept.extra, so the client doesn't hardcode any contract addresses.
681
+ */
682
+ async buildApprovalTransaction(accept, wallet, rpcUrl) {
683
+ const { payTo, asset, extra } = accept;
684
+ const amount = accept.amount ?? accept.maxAmountRequired;
685
+ if (!amount) {
686
+ throw new Error("Missing amount in payment requirements");
687
+ }
688
+ const facilitatorContract = extra?.facilitatorContract;
689
+ if (!facilitatorContract) {
690
+ throw new Error(
691
+ "exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
692
+ );
693
+ }
694
+ if (!wallet.signTypedData) {
695
+ throw new Error("Wallet does not support signTypedData (EIP-712)");
696
+ }
697
+ this.log("Building approval-based transaction:", {
698
+ from: wallet.address,
699
+ to: payTo,
700
+ amount,
701
+ asset,
702
+ network: accept.network,
703
+ facilitatorContract
704
+ });
705
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
706
+ const fee = extra?.fee ?? "0";
707
+ const totalNeeded = BigInt(amount) + BigInt(fee);
708
+ const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
709
+ if (currentAllowance < totalNeeded) {
710
+ if (!wallet.sendTransaction) {
711
+ throw new Error(
712
+ "BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
713
+ );
714
+ }
715
+ const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
716
+ this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
717
+ const approveTxHash = await wallet.sendTransaction({
718
+ to: asset,
719
+ data: this.encodeApprove(facilitatorContract, approvalAmount),
720
+ value: 0n
721
+ });
722
+ this.log(`Approval tx sent: ${approveTxHash}`);
723
+ await this.waitForReceipt(url, approveTxHash);
724
+ this.log("Approval confirmed");
725
+ } else {
726
+ this.log("Sufficient allowance, skipping approval");
727
+ }
728
+ const nonceBytes = new Uint8Array(16);
729
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
730
+ const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
731
+ const paymentIdBytes = new Uint8Array(32);
732
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
733
+ const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
734
+ const now = Math.floor(Date.now() / 1e3);
735
+ const deadline = now + (accept.maxTimeoutSeconds || 300);
736
+ const eip712Domain = extra?.eip712Domain;
737
+ const domain = eip712Domain ? {
738
+ name: eip712Domain.name,
739
+ version: eip712Domain.version,
740
+ chainId: BigInt(eip712Domain.chainId),
741
+ verifyingContract: eip712Domain.verifyingContract
742
+ } : {
743
+ name: "DexterBSCFacilitator",
744
+ version: "1",
745
+ chainId: BigInt(this.getChainId(accept.network)),
746
+ verifyingContract: facilitatorContract
747
+ };
748
+ const types = extra?.eip712Types ?? {
749
+ Payment: [
750
+ { name: "from", type: "address" },
751
+ { name: "to", type: "address" },
752
+ { name: "token", type: "address" },
753
+ { name: "amount", type: "uint256" },
754
+ { name: "fee", type: "uint256" },
755
+ { name: "nonce", type: "uint256" },
756
+ { name: "deadline", type: "uint256" },
757
+ { name: "paymentId", type: "bytes32" }
758
+ ]
759
+ };
760
+ const message = {
761
+ from: wallet.address,
762
+ to: payTo,
763
+ token: asset,
764
+ amount: BigInt(amount),
765
+ fee: BigInt(fee),
766
+ nonce: BigInt(nonce),
767
+ deadline: BigInt(deadline),
768
+ paymentId
769
+ };
770
+ const signature = await wallet.signTypedData({
771
+ domain,
772
+ types,
773
+ primaryType: "Payment",
774
+ message
775
+ });
776
+ this.log("EIP-712 Payment signature obtained");
777
+ const payload = {
778
+ from: wallet.address,
779
+ to: payTo,
780
+ token: asset,
781
+ amount,
782
+ fee,
783
+ nonce,
784
+ deadline,
785
+ paymentId,
786
+ signature
787
+ };
788
+ return {
789
+ serialized: JSON.stringify(payload),
790
+ signature
791
+ };
792
+ }
793
+ // ===========================================================================
794
+ // Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
795
+ // ===========================================================================
796
+ /**
797
+ * Build a Permit2 payment transaction. Used when the facilitator signals
798
+ * assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
799
+ *
800
+ * Flow:
801
+ * 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
802
+ * 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
803
+ * 3. Return { permit2Authorization, signature } payload for the facilitator.
804
+ */
805
+ async buildPermit2Transaction(accept, wallet, rpcUrl) {
806
+ const { payTo, asset } = accept;
807
+ const amount = accept.amount ?? accept.maxAmountRequired;
808
+ if (!amount) {
809
+ throw new Error("Missing amount in payment requirements");
810
+ }
811
+ if (!wallet.signTypedData) {
812
+ throw new Error("Wallet does not support signTypedData (EIP-712)");
813
+ }
814
+ this.log("Building Permit2 transaction:", {
815
+ from: wallet.address,
816
+ to: payTo,
817
+ amount,
818
+ asset,
819
+ network: accept.network
820
+ });
821
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
822
+ const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
823
+ let approvalExtension;
824
+ if (currentAllowance < BigInt(amount)) {
825
+ const approveData = this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256);
826
+ if (wallet.signTransaction) {
827
+ this.log(`Signing Permit2 approval for relay (current allowance: ${currentAllowance})`);
828
+ const chainId2 = this.getChainId(accept.network);
829
+ const gasPrice = await this.readGasPrice(url);
830
+ const nonce2 = await this.readNonce(url, wallet.address);
831
+ const signedTx = await wallet.signTransaction({
832
+ to: asset,
833
+ data: approveData,
834
+ chainId: chainId2,
835
+ gas: 50000n,
836
+ // standard ERC-20 approve
837
+ gasPrice,
838
+ nonce: nonce2
839
+ });
840
+ approvalExtension = {
841
+ erc20ApprovalGasSponsoring: {
842
+ info: {
843
+ from: wallet.address,
844
+ asset,
845
+ spender: PERMIT2_ADDRESS,
846
+ amount: MAX_UINT256.toString(),
847
+ signedTransaction: signedTx,
848
+ version: "1"
849
+ }
850
+ }
851
+ };
852
+ this.log("Permit2 approval signed for facilitator relay");
853
+ } else if (wallet.sendTransaction) {
854
+ this.log(`Approving Permit2 directly (current allowance: ${currentAllowance})`);
855
+ const approveTxHash = await wallet.sendTransaction({
856
+ to: asset,
857
+ data: approveData,
858
+ value: 0n
859
+ });
860
+ this.log(`Permit2 approval tx sent: ${approveTxHash}`);
861
+ await this.waitForReceipt(url, approveTxHash);
862
+ this.log("Permit2 approval confirmed");
863
+ } else {
864
+ throw new Error(
865
+ "Permit2 payments require a wallet that supports signTransaction or sendTransaction for the one-time Permit2 approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
866
+ );
867
+ }
868
+ } else {
869
+ this.log("Sufficient Permit2 allowance, skipping approval");
870
+ }
871
+ const nonceBytes = new Uint8Array(32);
872
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
873
+ const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
874
+ const now = Math.floor(Date.now() / 1e3);
875
+ const validAfter = now - 600;
876
+ const deadline = now + (accept.maxTimeoutSeconds || 300);
877
+ const chainId = this.getChainId(accept.network);
878
+ const domain = {
879
+ name: "Permit2",
880
+ chainId: BigInt(chainId),
881
+ verifyingContract: PERMIT2_ADDRESS
882
+ };
883
+ const message = {
884
+ permitted: {
885
+ token: asset,
886
+ amount: BigInt(amount)
887
+ },
888
+ spender: X402_EXACT_PERMIT2_PROXY,
889
+ nonce,
890
+ deadline: BigInt(deadline),
891
+ witness: {
892
+ to: payTo,
893
+ validAfter: BigInt(validAfter)
894
+ }
895
+ };
896
+ const signature = await wallet.signTypedData({
897
+ domain,
898
+ types: PERMIT2_WITNESS_TYPES,
899
+ primaryType: "PermitWitnessTransferFrom",
900
+ message
901
+ });
902
+ this.log("Permit2 PermitWitnessTransferFrom signature obtained");
903
+ const payload = {
904
+ signature,
905
+ permit2Authorization: {
906
+ from: wallet.address,
907
+ permitted: {
908
+ token: asset,
909
+ amount
910
+ },
911
+ spender: X402_EXACT_PERMIT2_PROXY,
912
+ nonce: nonce.toString(),
913
+ deadline: String(deadline),
914
+ witness: {
915
+ to: payTo,
916
+ validAfter: String(validAfter)
917
+ }
918
+ }
919
+ };
920
+ return {
921
+ serialized: JSON.stringify(payload),
922
+ signature,
923
+ extensions: approvalExtension
924
+ };
925
+ }
926
+ /**
927
+ * Read ERC-20 allowance via raw eth_call (no viem dependency needed).
928
+ */
929
+ async readAllowance(rpcUrl, token, owner, spender) {
930
+ const selector = "0xdd62ed3e";
931
+ const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
932
+ const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
933
+ const data = selector + paddedOwner + paddedSpender;
934
+ try {
935
+ const response = await fetch(rpcUrl, {
936
+ method: "POST",
937
+ headers: { "Content-Type": "application/json" },
938
+ body: JSON.stringify({
939
+ jsonrpc: "2.0",
940
+ id: 1,
941
+ method: "eth_call",
942
+ params: [{ to: token, data }, "latest"]
943
+ })
944
+ });
945
+ const result = await response.json();
946
+ if (result.error || !result.result || result.result === "0x") return 0n;
947
+ return BigInt(result.result);
948
+ } catch {
949
+ return 0n;
950
+ }
951
+ }
952
+ /**
953
+ * Encode ERC-20 approve(address,uint256) calldata.
954
+ */
955
+ encodeApprove(spender, amount) {
956
+ const selector = "0x095ea7b3";
957
+ const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
958
+ const paddedAmount = amount.toString(16).padStart(64, "0");
959
+ return selector + paddedSpender + paddedAmount;
960
+ }
961
+ /**
962
+ * Wait for a transaction receipt by polling eth_getTransactionReceipt.
963
+ */
964
+ async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
965
+ const start = Date.now();
966
+ while (Date.now() - start < timeoutMs) {
967
+ try {
968
+ const response = await fetch(rpcUrl, {
969
+ method: "POST",
970
+ headers: { "Content-Type": "application/json" },
971
+ body: JSON.stringify({
972
+ jsonrpc: "2.0",
973
+ id: 1,
974
+ method: "eth_getTransactionReceipt",
975
+ params: [txHash]
976
+ })
977
+ });
978
+ const result = await response.json();
979
+ if (result.result) {
980
+ if (result.result.status === "0x0") {
981
+ throw new Error(`Approval transaction reverted: ${txHash}`);
982
+ }
983
+ return;
984
+ }
985
+ } catch (err) {
986
+ if (err instanceof Error && err.message.includes("reverted")) throw err;
987
+ }
988
+ await new Promise((r) => setTimeout(r, 2e3));
989
+ }
990
+ throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
991
+ }
992
+ /**
993
+ * Read gas price via eth_gasPrice RPC call.
994
+ */
995
+ async readGasPrice(rpcUrl) {
996
+ try {
997
+ const response = await fetch(rpcUrl, {
998
+ method: "POST",
999
+ headers: { "Content-Type": "application/json" },
1000
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_gasPrice", params: [] })
1001
+ });
1002
+ const result = await response.json();
1003
+ return result.result ? BigInt(result.result) : 50000000n;
1004
+ } catch {
1005
+ return 50000000n;
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Read transaction count (nonce) via eth_getTransactionCount RPC call.
1010
+ */
1011
+ async readNonce(rpcUrl, address) {
1012
+ try {
1013
+ const response = await fetch(rpcUrl, {
1014
+ method: "POST",
1015
+ headers: { "Content-Type": "application/json" },
1016
+ body: JSON.stringify({
1017
+ jsonrpc: "2.0",
1018
+ id: 1,
1019
+ method: "eth_getTransactionCount",
1020
+ params: [address, "latest"]
1021
+ })
1022
+ });
1023
+ const result = await response.json();
1024
+ return result.result ? parseInt(result.result, 16) : 0;
1025
+ } catch {
1026
+ return 0;
1027
+ }
1028
+ }
1029
+ /**
1030
+ * Calculate how much to approve based on the facilitator's approval strategy.
1031
+ * Buffered approvals reduce the number of on-chain approval txs for micropayments.
1032
+ */
1033
+ calculateApprovalAmount(paymentAmount, fee, strategy) {
1034
+ const total = BigInt(paymentAmount) + BigInt(fee);
1035
+ if (!strategy || strategy.mode === "exact") {
1036
+ return total;
1037
+ }
1038
+ const multiple = BigInt(strategy.defaultMultiple ?? 10);
1039
+ const buffered = total * multiple;
1040
+ if (strategy.maxCapUsd) {
1041
+ const decimals = this.inferDecimals(paymentAmount);
1042
+ const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
1043
+ if (buffered > maxCap) return maxCap;
1044
+ }
1045
+ if (strategy.exactAboveUsd) {
1046
+ const decimals = this.inferDecimals(paymentAmount);
1047
+ const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
1048
+ if (BigInt(paymentAmount) > threshold) return total;
1049
+ }
1050
+ return buffered;
1051
+ }
1052
+ /**
1053
+ * Infer token decimals from payment amount magnitude.
1054
+ * BSC stablecoins use 18 decimals, all others use 6.
1055
+ * A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
1056
+ * If the amount has > 12 digits, it's almost certainly 18 decimals.
1057
+ */
1058
+ inferDecimals(amount) {
1059
+ return amount.length > 12 ? 18 : 6;
1060
+ }
635
1061
  };
636
1062
  function createEvmAdapter(config) {
637
1063
  return new EvmAdapter(config);
@@ -645,6 +1071,9 @@ function isKnownUSDC(asset) {
645
1071
  for (const addr of Object.values(USDC_ADDRESSES)) {
646
1072
  if (addr.toLowerCase() === lc) return true;
647
1073
  }
1074
+ for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
1075
+ if (addr.toLowerCase() === lc) return true;
1076
+ }
648
1077
  return false;
649
1078
  }
650
1079
 
@@ -753,6 +1182,19 @@ function createX402Client(config) {
753
1182
  }
754
1183
  return candidates[0];
755
1184
  }
1185
+ function getChainDisplayName(network, adapterName) {
1186
+ const names = {
1187
+ "eip155:56": "BSC",
1188
+ "eip155:8453": "Base",
1189
+ "eip155:84532": "Base Sepolia",
1190
+ "eip155:42161": "Arbitrum",
1191
+ "eip155:137": "Polygon",
1192
+ "eip155:10": "Optimism",
1193
+ "eip155:43114": "Avalanche",
1194
+ "eip155:1": "Ethereum"
1195
+ };
1196
+ return names[network] || adapterName;
1197
+ }
756
1198
  function getRpcUrl(network, adapter) {
757
1199
  return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
758
1200
  }
@@ -845,6 +1287,9 @@ function createX402Client(config) {
845
1287
  accepted: accept,
846
1288
  payload
847
1289
  };
1290
+ if (signedTx.extensions) {
1291
+ paymentSignature.extensions = signedTx.extensions;
1292
+ }
848
1293
  const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
849
1294
  const passResponse = await customFetch(passUrl, {
850
1295
  ...init,
@@ -967,10 +1412,10 @@ function createX402Client(config) {
967
1412
  const balance = await adapter.getBalance(accept, wallet, rpcUrl);
968
1413
  const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
969
1414
  if (balance < requiredAmount) {
970
- const network = adapter.name === "EVM" ? "Base" : "Solana";
1415
+ const chainName = getChainDisplayName(accept.network, adapter.name);
971
1416
  throw new X402Error(
972
1417
  "insufficient_balance",
973
- `Insufficient USDC balance on ${network}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
1418
+ `Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
974
1419
  );
975
1420
  }
976
1421
  log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
@@ -1023,6 +1468,9 @@ function createX402Client(config) {
1023
1468
  accepted: accept,
1024
1469
  payload
1025
1470
  };
1471
+ if (signedTx.extensions) {
1472
+ paymentSignature.extensions = signedTx.extensions;
1473
+ }
1026
1474
  const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
1027
1475
  log("Retrying request with payment...");
1028
1476
  const retryResponse = await fetchWithRetry(input, {
@@ -1151,7 +1599,16 @@ async function createEvmKeypairWallet(privateKey) {
1151
1599
  const account = privateKeyToAccount(normalizedKey);
1152
1600
  return {
1153
1601
  address: account.address,
1154
- signTypedData: (params) => account.signTypedData(params)
1602
+ signTypedData: (params) => account.signTypedData(params),
1603
+ signTransaction: (params) => account.signTransaction({
1604
+ to: params.to,
1605
+ data: params.data,
1606
+ chainId: params.chainId,
1607
+ gas: params.gas,
1608
+ gasPrice: params.gasPrice,
1609
+ nonce: params.nonce,
1610
+ type: "legacy"
1611
+ })
1155
1612
  };
1156
1613
  }
1157
1614
  function isEvmKeypairWallet(wallet) {
@@ -1218,55 +1675,84 @@ function wrapFetch(fetchImpl, options) {
1218
1675
  }
1219
1676
 
1220
1677
  // src/client/discovery.ts
1221
- var DEFAULT_MARKETPLACE = "https://x402.dexter.cash/api/facilitator/marketplace/resources";
1222
- async function searchAPIs(options = {}) {
1678
+ var DEFAULT_CAPABILITY_ENDPOINT = "https://x402.dexter.cash/api/x402gle/capability";
1679
+ function formatPriceLabel(priceUsdc) {
1680
+ if (priceUsdc == null) return "price on request";
1681
+ if (priceUsdc === 0) return "free";
1682
+ if (priceUsdc < 0.01) return `$${priceUsdc.toFixed(4)}`;
1683
+ return `$${priceUsdc.toFixed(2)}`;
1684
+ }
1685
+ function mapResult(r) {
1686
+ return {
1687
+ resourceId: r.resourceId,
1688
+ name: r.displayName ?? r.resourceUrl,
1689
+ url: r.resourceUrl,
1690
+ method: r.method || "GET",
1691
+ price: formatPriceLabel(r.pricing.usdc),
1692
+ priceUsdc: r.pricing.usdc,
1693
+ network: r.pricing.network,
1694
+ description: r.description ?? "",
1695
+ category: r.category ?? "uncategorized",
1696
+ qualityScore: r.verification.qualityScore,
1697
+ verified: r.verification.status === "pass",
1698
+ verificationStatus: r.verification.status,
1699
+ totalCalls: r.usage.totalSettlements,
1700
+ totalVolumeUsdc: r.usage.totalVolumeUsdc,
1701
+ iconUrl: r.icon,
1702
+ host: r.host,
1703
+ gamingFlags: r.gaming.flags,
1704
+ gamingSuspicious: r.gaming.suspicious,
1705
+ tier: r.tier,
1706
+ similarity: Math.round(r.similarity * 1e3) / 1e3,
1707
+ why: r.why,
1708
+ score: r.score
1709
+ };
1710
+ }
1711
+ async function capabilitySearch(options) {
1712
+ if (!options?.query || !options.query.trim()) {
1713
+ throw new Error("capabilitySearch: query is required");
1714
+ }
1223
1715
  const {
1224
1716
  query,
1225
- category,
1226
- network,
1227
- maxPrice,
1228
- verifiedOnly,
1229
- sort = "marketplace",
1230
1717
  limit = 20,
1231
- marketplaceUrl = DEFAULT_MARKETPLACE
1718
+ unverified,
1719
+ testnets,
1720
+ rerank,
1721
+ endpoint = DEFAULT_CAPABILITY_ENDPOINT
1232
1722
  } = options;
1233
1723
  const params = new URLSearchParams();
1234
- if (query) params.set("search", query);
1235
- if (category) params.set("category", category);
1236
- if (network) params.set("network", network);
1237
- if (maxPrice !== void 0) params.set("maxPrice", String(maxPrice));
1238
- if (verifiedOnly) params.set("verified", "true");
1239
- params.set("sort", sort);
1240
- params.set("order", "desc");
1241
- params.set("limit", String(Math.min(limit, 50)));
1242
- const url = `${marketplaceUrl}?${params.toString()}`;
1724
+ params.set("q", query);
1725
+ params.set("limit", String(Math.min(Math.max(limit, 1), 50)));
1726
+ if (unverified) params.set("unverified", "true");
1727
+ if (testnets) params.set("testnets", "true");
1728
+ if (rerank === false) params.set("rerank", "false");
1729
+ const url = `${endpoint}?${params.toString()}`;
1243
1730
  const response = await fetch(url, {
1244
- headers: { "Accept": "application/json" },
1245
- signal: AbortSignal.timeout(15e3)
1731
+ headers: { Accept: "application/json" },
1732
+ signal: AbortSignal.timeout(2e4)
1246
1733
  });
1247
1734
  if (!response.ok) {
1248
- throw new Error(`Marketplace search failed: ${response.status}`);
1735
+ const body = await response.text().catch(() => "");
1736
+ throw new Error(`Capability search failed: ${response.status} ${body.slice(0, 400)}`);
1249
1737
  }
1250
1738
  const data = await response.json();
1251
- if (!data.resources) return [];
1252
- return data.resources.map((r) => ({
1253
- name: r.displayName || r.resourceUrl,
1254
- url: r.resourceUrl,
1255
- method: r.method || "GET",
1256
- price: r.priceLabel || (r.priceUsdc ? `$${r.priceUsdc.toFixed(4)}` : "free"),
1257
- priceUsdc: r.priceUsdc ?? null,
1258
- network: r.priceNetwork ?? null,
1259
- description: r.description || "",
1260
- category: r.category || "uncategorized",
1261
- qualityScore: r.qualityScore ?? null,
1262
- verified: r.verificationStatus === "pass",
1263
- totalCalls: r.totalSettlements || 0,
1264
- totalVolume: r.totalVolumeUsdc ? `$${r.totalVolumeUsdc.toLocaleString("en-US", { minimumFractionDigits: 2 })}` : null,
1265
- seller: r.seller?.displayName ?? null,
1266
- sellerReputation: r.reputationScore ?? null,
1267
- authRequired: r.authRequired || false,
1268
- lastActive: r.lastSettlementAt ?? null
1269
- }));
1739
+ if (!data.ok) {
1740
+ throw new Error(
1741
+ `Capability search error${data.stage ? ` at stage ${data.stage}` : ""}: ${data.error ?? "unknown"}`
1742
+ );
1743
+ }
1744
+ return {
1745
+ query: data.query,
1746
+ strongResults: data.strongResults.map(mapResult),
1747
+ relatedResults: data.relatedResults.map(mapResult),
1748
+ strongCount: data.strongCount,
1749
+ relatedCount: data.relatedCount,
1750
+ topSimilarity: data.topSimilarity,
1751
+ noMatchReason: data.noMatchReason,
1752
+ rerank: data.rerank,
1753
+ intent: data.intent,
1754
+ durationMs: data.durationMs
1755
+ };
1270
1756
  }
1271
1757
 
1272
1758
  // src/client/budget-account.ts
@@ -1404,6 +1890,7 @@ async function fireImpressionBeacon(response) {
1404
1890
  SOLANA_MAINNET,
1405
1891
  USDC_MINT,
1406
1892
  X402Error,
1893
+ capabilitySearch,
1407
1894
  createBudgetAccount,
1408
1895
  createEvmAdapter,
1409
1896
  createEvmKeypairWallet,
@@ -1416,6 +1903,5 @@ async function fireImpressionBeacon(response) {
1416
1903
  getSponsoredRecommendations,
1417
1904
  isEvmKeypairWallet,
1418
1905
  isKeypairWallet,
1419
- searchAPIs,
1420
1906
  wrapFetch
1421
1907
  });