@dexterai/x402 1.9.4 → 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.
Files changed (40) hide show
  1. package/README.md +27 -0
  2. package/dist/adapters/index.cjs +375 -3
  3. package/dist/adapters/index.d.cts +4 -5
  4. package/dist/adapters/index.d.ts +4 -5
  5. package/dist/adapters/index.js +369 -3
  6. package/dist/client/index.cjs +570 -10
  7. package/dist/client/index.d.cts +200 -36
  8. package/dist/client/index.d.ts +200 -36
  9. package/dist/client/index.js +568 -10
  10. package/dist/react/index.cjs +404 -8
  11. package/dist/react/index.d.cts +5 -5
  12. package/dist/react/index.d.ts +5 -5
  13. package/dist/react/index.js +404 -8
  14. package/dist/server/index.cjs +3 -4
  15. package/dist/server/index.d.cts +2 -2
  16. package/dist/server/index.d.ts +2 -2
  17. package/dist/server/index.js +3 -4
  18. package/dist/{sponsored-access-D1_mINs4.d.ts → sponsored-access-DAVzu4x6.d.cts} +13 -2
  19. package/dist/{sponsored-access-Br6YPA-m.d.cts → sponsored-access-Lxa11w_X.d.ts} +13 -2
  20. package/dist/types-D1u7iu8n.d.cts +304 -0
  21. package/dist/types-YQlJI5E3.d.ts +304 -0
  22. package/dist/{types-CjLMR7qs.d.cts → types-_iT11DL0.d.cts} +2 -2
  23. package/dist/{types-CjLMR7qs.d.ts → types-_iT11DL0.d.ts} +2 -2
  24. package/dist/utils/index.cjs +0 -1
  25. package/dist/utils/index.js +0 -1
  26. package/package.json +1 -1
  27. package/dist/adapters/index.cjs.map +0 -1
  28. package/dist/adapters/index.js.map +0 -1
  29. package/dist/client/index.cjs.map +0 -1
  30. package/dist/client/index.js.map +0 -1
  31. package/dist/react/index.cjs.map +0 -1
  32. package/dist/react/index.js.map +0 -1
  33. package/dist/server/index.cjs.map +0 -1
  34. package/dist/server/index.js.map +0 -1
  35. package/dist/solana-BcOfK6Eq.d.cts +0 -132
  36. package/dist/solana-Cxr5byPa.d.ts +0 -132
  37. package/dist/types-BIHhO2-I.d.ts +0 -123
  38. package/dist/types-CfKflCZO.d.cts +0 -123
  39. package/dist/utils/index.cjs.map +0 -1
  40. package/dist/utils/index.js.map +0 -1
@@ -191,6 +191,7 @@ __export(client_exports, {
191
191
  SOLANA_MAINNET: () => SOLANA_MAINNET,
192
192
  USDC_MINT: () => USDC_MINT,
193
193
  X402Error: () => X402Error,
194
+ createBudgetAccount: () => createBudgetAccount,
194
195
  createEvmAdapter: () => createEvmAdapter,
195
196
  createEvmKeypairWallet: () => createEvmKeypairWallet,
196
197
  createKeypairWallet: () => createKeypairWallet,
@@ -202,6 +203,7 @@ __export(client_exports, {
202
203
  getSponsoredRecommendations: () => getSponsoredRecommendations,
203
204
  isEvmKeypairWallet: () => isEvmKeypairWallet,
204
205
  isKeypairWallet: () => isKeypairWallet,
206
+ searchAPIs: () => searchAPIs,
205
207
  wrapFetch: () => wrapFetch
206
208
  });
207
209
  module.exports = __toCommonJS(client_exports);
@@ -412,16 +414,40 @@ function createSolanaAdapter(config) {
412
414
  }
413
415
 
414
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");
415
437
  var BASE_MAINNET = "eip155:8453";
416
438
  var BASE_SEPOLIA = "eip155:84532";
417
439
  var ARBITRUM_ONE = "eip155:42161";
418
440
  var POLYGON = "eip155:137";
419
441
  var OPTIMISM = "eip155:10";
420
442
  var AVALANCHE = "eip155:43114";
443
+ var BSC_MAINNET = "eip155:56";
421
444
  var SKALE_BASE = "eip155:1187947933";
422
445
  var SKALE_BASE_SEPOLIA = "eip155:324705682";
423
446
  var ETHEREUM_MAINNET = "eip155:1";
447
+ var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
448
+ var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
424
449
  var CHAIN_IDS = {
450
+ [BSC_MAINNET]: 56,
425
451
  [BASE_MAINNET]: 8453,
426
452
  [BASE_SEPOLIA]: 84532,
427
453
  [ARBITRUM_ONE]: 42161,
@@ -433,6 +459,7 @@ var CHAIN_IDS = {
433
459
  [ETHEREUM_MAINNET]: 1
434
460
  };
435
461
  var DEFAULT_RPC_URLS2 = {
462
+ [BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
436
463
  [BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
437
464
  [BASE_SEPOLIA]: "https://sepolia.base.org",
438
465
  [ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
@@ -444,6 +471,7 @@ var DEFAULT_RPC_URLS2 = {
444
471
  [ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
445
472
  };
446
473
  var USDC_ADDRESSES = {
474
+ [BSC_MAINNET]: BSC_USDC,
447
475
  [BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
448
476
  [BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
449
477
  [ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
@@ -454,6 +482,10 @@ var USDC_ADDRESSES = {
454
482
  [SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
455
483
  [ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
456
484
  };
485
+ var BSC_STABLECOIN_ADDRESSES = {
486
+ [BSC_USDT]: { symbol: "USDT", decimals: 18 },
487
+ [BSC_USDC]: { symbol: "USDC", decimals: 18 }
488
+ };
457
489
  function isEvmWallet(wallet) {
458
490
  if (!wallet || typeof wallet !== "object") return false;
459
491
  const w = wallet;
@@ -461,7 +493,7 @@ function isEvmWallet(wallet) {
461
493
  }
462
494
  var EvmAdapter = class {
463
495
  name = "EVM";
464
- networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
496
+ networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
465
497
  config;
466
498
  log;
467
499
  constructor(config = {}) {
@@ -472,6 +504,7 @@ var EvmAdapter = class {
472
504
  canHandle(network) {
473
505
  if (this.networks.includes(network)) return true;
474
506
  if (network === "base") return true;
507
+ if (network === "bsc") return true;
475
508
  if (network === "ethereum") return true;
476
509
  if (network === "arbitrum") return true;
477
510
  if (network.startsWith("eip155:")) return true;
@@ -485,6 +518,7 @@ var EvmAdapter = class {
485
518
  return DEFAULT_RPC_URLS2[network];
486
519
  }
487
520
  if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
521
+ if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
488
522
  if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
489
523
  if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
490
524
  return DEFAULT_RPC_URLS2[BASE_MAINNET];
@@ -504,6 +538,7 @@ var EvmAdapter = class {
504
538
  return parseInt(chainIdStr, 10);
505
539
  }
506
540
  if (network === "base") return 8453;
541
+ if (network === "bsc") return 56;
507
542
  if (network === "ethereum") return 1;
508
543
  if (network === "arbitrum") return 42161;
509
544
  return 8453;
@@ -553,13 +588,19 @@ var EvmAdapter = class {
553
588
  const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
554
589
  return selector + paddedAddress;
555
590
  }
556
- async buildTransaction(accept, wallet, _rpcUrl) {
591
+ async buildTransaction(accept, wallet, rpcUrl) {
557
592
  if (!isEvmWallet(wallet)) {
558
593
  throw new Error("Invalid EVM wallet");
559
594
  }
560
595
  if (!wallet.address) {
561
596
  throw new Error("Wallet not connected");
562
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
+ }
563
604
  const { payTo, asset, extra } = accept;
564
605
  const amount = accept.amount ?? accept.maxAmountRequired;
565
606
  if (!amount) {
@@ -630,6 +671,325 @@ var EvmAdapter = class {
630
671
  signature
631
672
  };
632
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
+ if (currentAllowance < BigInt(amount)) {
824
+ if (!wallet.sendTransaction) {
825
+ throw new Error(
826
+ "Permit2 payments require a wallet that supports sendTransaction for the one-time Permit2 approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
827
+ );
828
+ }
829
+ this.log(`Approving Permit2 for ${asset} (current allowance: ${currentAllowance})`);
830
+ const approveTxHash = await wallet.sendTransaction({
831
+ to: asset,
832
+ data: this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256),
833
+ value: 0n
834
+ });
835
+ this.log(`Permit2 approval tx sent: ${approveTxHash}`);
836
+ await this.waitForReceipt(url, approveTxHash);
837
+ this.log("Permit2 approval confirmed");
838
+ } else {
839
+ this.log("Sufficient Permit2 allowance, skipping approval");
840
+ }
841
+ const nonceBytes = new Uint8Array(32);
842
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
843
+ const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
844
+ const now = Math.floor(Date.now() / 1e3);
845
+ const validAfter = now - 600;
846
+ const deadline = now + (accept.maxTimeoutSeconds || 300);
847
+ const chainId = this.getChainId(accept.network);
848
+ const domain = {
849
+ name: "Permit2",
850
+ chainId: BigInt(chainId),
851
+ verifyingContract: PERMIT2_ADDRESS
852
+ };
853
+ const message = {
854
+ permitted: {
855
+ token: asset,
856
+ amount: BigInt(amount)
857
+ },
858
+ spender: X402_EXACT_PERMIT2_PROXY,
859
+ nonce,
860
+ deadline: BigInt(deadline),
861
+ witness: {
862
+ to: payTo,
863
+ validAfter: BigInt(validAfter)
864
+ }
865
+ };
866
+ const signature = await wallet.signTypedData({
867
+ domain,
868
+ types: PERMIT2_WITNESS_TYPES,
869
+ primaryType: "PermitWitnessTransferFrom",
870
+ message
871
+ });
872
+ this.log("Permit2 PermitWitnessTransferFrom signature obtained");
873
+ const payload = {
874
+ signature,
875
+ permit2Authorization: {
876
+ from: wallet.address,
877
+ permitted: {
878
+ token: asset,
879
+ amount
880
+ },
881
+ spender: X402_EXACT_PERMIT2_PROXY,
882
+ nonce: nonce.toString(),
883
+ deadline: String(deadline),
884
+ witness: {
885
+ to: payTo,
886
+ validAfter: String(validAfter)
887
+ }
888
+ }
889
+ };
890
+ return {
891
+ serialized: JSON.stringify(payload),
892
+ signature
893
+ };
894
+ }
895
+ /**
896
+ * Read ERC-20 allowance via raw eth_call (no viem dependency needed).
897
+ */
898
+ async readAllowance(rpcUrl, token, owner, spender) {
899
+ const selector = "0xdd62ed3e";
900
+ const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
901
+ const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
902
+ const data = selector + paddedOwner + paddedSpender;
903
+ try {
904
+ const response = await fetch(rpcUrl, {
905
+ method: "POST",
906
+ headers: { "Content-Type": "application/json" },
907
+ body: JSON.stringify({
908
+ jsonrpc: "2.0",
909
+ id: 1,
910
+ method: "eth_call",
911
+ params: [{ to: token, data }, "latest"]
912
+ })
913
+ });
914
+ const result = await response.json();
915
+ if (result.error || !result.result || result.result === "0x") return 0n;
916
+ return BigInt(result.result);
917
+ } catch {
918
+ return 0n;
919
+ }
920
+ }
921
+ /**
922
+ * Encode ERC-20 approve(address,uint256) calldata.
923
+ */
924
+ encodeApprove(spender, amount) {
925
+ const selector = "0x095ea7b3";
926
+ const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
927
+ const paddedAmount = amount.toString(16).padStart(64, "0");
928
+ return selector + paddedSpender + paddedAmount;
929
+ }
930
+ /**
931
+ * Wait for a transaction receipt by polling eth_getTransactionReceipt.
932
+ */
933
+ async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
934
+ const start = Date.now();
935
+ while (Date.now() - start < timeoutMs) {
936
+ try {
937
+ const response = await fetch(rpcUrl, {
938
+ method: "POST",
939
+ headers: { "Content-Type": "application/json" },
940
+ body: JSON.stringify({
941
+ jsonrpc: "2.0",
942
+ id: 1,
943
+ method: "eth_getTransactionReceipt",
944
+ params: [txHash]
945
+ })
946
+ });
947
+ const result = await response.json();
948
+ if (result.result) {
949
+ if (result.result.status === "0x0") {
950
+ throw new Error(`Approval transaction reverted: ${txHash}`);
951
+ }
952
+ return;
953
+ }
954
+ } catch (err) {
955
+ if (err instanceof Error && err.message.includes("reverted")) throw err;
956
+ }
957
+ await new Promise((r) => setTimeout(r, 2e3));
958
+ }
959
+ throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
960
+ }
961
+ /**
962
+ * Calculate how much to approve based on the facilitator's approval strategy.
963
+ * Buffered approvals reduce the number of on-chain approval txs for micropayments.
964
+ */
965
+ calculateApprovalAmount(paymentAmount, fee, strategy) {
966
+ const total = BigInt(paymentAmount) + BigInt(fee);
967
+ if (!strategy || strategy.mode === "exact") {
968
+ return total;
969
+ }
970
+ const multiple = BigInt(strategy.defaultMultiple ?? 10);
971
+ const buffered = total * multiple;
972
+ if (strategy.maxCapUsd) {
973
+ const decimals = this.inferDecimals(paymentAmount);
974
+ const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
975
+ if (buffered > maxCap) return maxCap;
976
+ }
977
+ if (strategy.exactAboveUsd) {
978
+ const decimals = this.inferDecimals(paymentAmount);
979
+ const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
980
+ if (BigInt(paymentAmount) > threshold) return total;
981
+ }
982
+ return buffered;
983
+ }
984
+ /**
985
+ * Infer token decimals from payment amount magnitude.
986
+ * BSC stablecoins use 18 decimals, all others use 6.
987
+ * A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
988
+ * If the amount has > 12 digits, it's almost certainly 18 decimals.
989
+ */
990
+ inferDecimals(amount) {
991
+ return amount.length > 12 ? 18 : 6;
992
+ }
633
993
  };
634
994
  function createEvmAdapter(config) {
635
995
  return new EvmAdapter(config);
@@ -643,6 +1003,9 @@ function isKnownUSDC(asset) {
643
1003
  for (const addr of Object.values(USDC_ADDRESSES)) {
644
1004
  if (addr.toLowerCase() === lc) return true;
645
1005
  }
1006
+ for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
1007
+ if (addr.toLowerCase() === lc) return true;
1008
+ }
646
1009
  return false;
647
1010
  }
648
1011
 
@@ -662,10 +1025,33 @@ function createX402Client(config) {
662
1025
  fetch: customFetch = globalThis.fetch,
663
1026
  verbose = false,
664
1027
  accessPass: accessPassConfig,
665
- onPaymentRequired
1028
+ onPaymentRequired,
1029
+ maxRetries = 0,
1030
+ retryDelayMs = 500
666
1031
  } = config;
667
1032
  const log = verbose ? console.log.bind(console, "[x402]") : () => {
668
1033
  };
1034
+ async function fetchWithRetry(input, init) {
1035
+ let lastError;
1036
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1037
+ try {
1038
+ const response = await customFetch(input, init);
1039
+ if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
1040
+ log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
1041
+ await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
1042
+ continue;
1043
+ }
1044
+ return response;
1045
+ } catch (err) {
1046
+ lastError = err;
1047
+ if (attempt < maxRetries) {
1048
+ log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
1049
+ await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
1050
+ }
1051
+ }
1052
+ }
1053
+ throw lastError;
1054
+ }
669
1055
  const passCache = /* @__PURE__ */ new Map();
670
1056
  function getCachedPass(url) {
671
1057
  try {
@@ -728,6 +1114,19 @@ function createX402Client(config) {
728
1114
  }
729
1115
  return candidates[0];
730
1116
  }
1117
+ function getChainDisplayName(network, adapterName) {
1118
+ const names = {
1119
+ "eip155:56": "BSC",
1120
+ "eip155:8453": "Base",
1121
+ "eip155:84532": "Base Sepolia",
1122
+ "eip155:42161": "Arbitrum",
1123
+ "eip155:137": "Polygon",
1124
+ "eip155:10": "Optimism",
1125
+ "eip155:43114": "Avalanche",
1126
+ "eip155:1": "Ethereum"
1127
+ };
1128
+ return names[network] || adapterName;
1129
+ }
731
1130
  function getRpcUrl(network, adapter) {
732
1131
  return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
733
1132
  }
@@ -865,7 +1264,7 @@ function createX402Client(config) {
865
1264
  }
866
1265
  }
867
1266
  }
868
- const response = await customFetch(input, init);
1267
+ const response = await fetchWithRetry(input, init);
869
1268
  if (response.status !== 402) {
870
1269
  return response;
871
1270
  }
@@ -942,10 +1341,10 @@ function createX402Client(config) {
942
1341
  const balance = await adapter.getBalance(accept, wallet, rpcUrl);
943
1342
  const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
944
1343
  if (balance < requiredAmount) {
945
- const network = adapter.name === "EVM" ? "Base" : "Solana";
1344
+ const chainName = getChainDisplayName(accept.network, adapter.name);
946
1345
  throw new X402Error(
947
1346
  "insufficient_balance",
948
- `Insufficient USDC balance on ${network}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
1347
+ `Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
949
1348
  );
950
1349
  }
951
1350
  log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
@@ -1000,7 +1399,7 @@ function createX402Client(config) {
1000
1399
  };
1001
1400
  const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
1002
1401
  log("Retrying request with payment...");
1003
- const retryResponse = await customFetch(input, {
1402
+ const retryResponse = await fetchWithRetry(input, {
1004
1403
  ...init,
1005
1404
  headers: {
1006
1405
  ...init?.headers || {},
@@ -1144,7 +1543,8 @@ function wrapFetch(fetchImpl, options) {
1144
1543
  rpcUrls,
1145
1544
  maxAmountAtomic,
1146
1545
  verbose,
1147
- accessPass
1546
+ accessPass,
1547
+ onPaymentRequired
1148
1548
  } = options;
1149
1549
  if (!walletPrivateKey && !evmPrivateKey) {
1150
1550
  throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");
@@ -1177,7 +1577,8 @@ function wrapFetch(fetchImpl, options) {
1177
1577
  maxAmountAtomic,
1178
1578
  fetch: fetchImpl,
1179
1579
  verbose,
1180
- accessPass
1580
+ accessPass,
1581
+ onPaymentRequired
1181
1582
  };
1182
1583
  const client = createX402Client(clientConfig);
1183
1584
  const clientFetch = client.fetch.bind(client);
@@ -1190,6 +1591,164 @@ function wrapFetch(fetchImpl, options) {
1190
1591
  return clientFetch;
1191
1592
  }
1192
1593
 
1594
+ // src/client/discovery.ts
1595
+ var DEFAULT_MARKETPLACE = "https://x402.dexter.cash/api/facilitator/marketplace/resources";
1596
+ async function searchAPIs(options = {}) {
1597
+ const {
1598
+ query,
1599
+ category,
1600
+ network,
1601
+ maxPrice,
1602
+ verifiedOnly,
1603
+ sort = "marketplace",
1604
+ limit = 20,
1605
+ marketplaceUrl = DEFAULT_MARKETPLACE
1606
+ } = options;
1607
+ const params = new URLSearchParams();
1608
+ if (query) params.set("search", query);
1609
+ if (category) params.set("category", category);
1610
+ if (network) params.set("network", network);
1611
+ if (maxPrice !== void 0) params.set("maxPrice", String(maxPrice));
1612
+ if (verifiedOnly) params.set("verified", "true");
1613
+ params.set("sort", sort);
1614
+ params.set("order", "desc");
1615
+ params.set("limit", String(Math.min(limit, 50)));
1616
+ const url = `${marketplaceUrl}?${params.toString()}`;
1617
+ const response = await fetch(url, {
1618
+ headers: { "Accept": "application/json" },
1619
+ signal: AbortSignal.timeout(15e3)
1620
+ });
1621
+ if (!response.ok) {
1622
+ throw new Error(`Marketplace search failed: ${response.status}`);
1623
+ }
1624
+ const data = await response.json();
1625
+ if (!data.resources) return [];
1626
+ return data.resources.map((r) => ({
1627
+ name: r.displayName || r.resourceUrl,
1628
+ url: r.resourceUrl,
1629
+ method: r.method || "GET",
1630
+ price: r.priceLabel || (r.priceUsdc ? `$${r.priceUsdc.toFixed(4)}` : "free"),
1631
+ priceUsdc: r.priceUsdc ?? null,
1632
+ network: r.priceNetwork ?? null,
1633
+ description: r.description || "",
1634
+ category: r.category || "uncategorized",
1635
+ qualityScore: r.qualityScore ?? null,
1636
+ verified: r.verificationStatus === "pass",
1637
+ totalCalls: r.totalSettlements || 0,
1638
+ totalVolume: r.totalVolumeUsdc ? `$${r.totalVolumeUsdc.toLocaleString("en-US", { minimumFractionDigits: 2 })}` : null,
1639
+ seller: r.seller?.displayName ?? null,
1640
+ sellerReputation: r.reputationScore ?? null,
1641
+ authRequired: r.authRequired || false,
1642
+ lastActive: r.lastSettlementAt ?? null
1643
+ }));
1644
+ }
1645
+
1646
+ // src/client/budget-account.ts
1647
+ function createBudgetAccount(config) {
1648
+ const { budget, allowedDomains, onPaymentRequired: userOnPayment, ...fetchOptions } = config;
1649
+ const totalBudget = parseFloat(budget.total);
1650
+ const perRequestMax = budget.perRequest ? parseFloat(budget.perRequest) : Infinity;
1651
+ const perHourMax = budget.perHour ? parseFloat(budget.perHour) : Infinity;
1652
+ if (isNaN(totalBudget) || totalBudget <= 0) {
1653
+ throw new Error("budget.total must be a positive number");
1654
+ }
1655
+ let ledger = [];
1656
+ let pendingAmount = 0;
1657
+ function getSpent() {
1658
+ return ledger.reduce((sum, r) => sum + r.amount, 0);
1659
+ }
1660
+ function getHourlySpend() {
1661
+ const cutoff = Date.now() - 36e5;
1662
+ return ledger.filter((r) => r.timestamp >= cutoff).reduce((sum, r) => sum + r.amount, 0);
1663
+ }
1664
+ const innerFetch = wrapFetch(fetch, {
1665
+ ...fetchOptions,
1666
+ onPaymentRequired: async (accept) => {
1667
+ const decimals = accept.extra?.decimals ?? 6;
1668
+ const amountUsd = Number(accept.amount) / Math.pow(10, decimals);
1669
+ if (amountUsd > perRequestMax) {
1670
+ throw new X402Error(
1671
+ "amount_exceeds_max",
1672
+ `$${amountUsd.toFixed(4)} exceeds per-request limit of $${perRequestMax.toFixed(2)}`
1673
+ );
1674
+ }
1675
+ const spent = getSpent();
1676
+ if (spent + amountUsd > totalBudget) {
1677
+ throw new X402Error(
1678
+ "amount_exceeds_max",
1679
+ `Budget exceeded. Spent $${spent.toFixed(2)} of $${totalBudget.toFixed(2)}, payment: $${amountUsd.toFixed(4)}`
1680
+ );
1681
+ }
1682
+ const hourly = getHourlySpend();
1683
+ if (hourly + amountUsd > perHourMax) {
1684
+ throw new X402Error(
1685
+ "amount_exceeds_max",
1686
+ `Hourly limit ($${perHourMax.toFixed(2)}) exceeded. Spent $${hourly.toFixed(2)} this hour`
1687
+ );
1688
+ }
1689
+ pendingAmount = amountUsd;
1690
+ if (userOnPayment) return userOnPayment(accept);
1691
+ return true;
1692
+ }
1693
+ });
1694
+ const budgetFetch = (async (input, init) => {
1695
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1696
+ let domain = "unknown";
1697
+ try {
1698
+ domain = new URL(url).hostname;
1699
+ } catch {
1700
+ }
1701
+ if (allowedDomains) {
1702
+ if (!allowedDomains.some((d) => domain === d || domain.endsWith(`.${d}`))) {
1703
+ throw new X402Error("payment_rejected", `Domain "${domain}" not in allowed domains`);
1704
+ }
1705
+ }
1706
+ pendingAmount = 0;
1707
+ const response = await innerFetch(input, init);
1708
+ if (pendingAmount > 0) {
1709
+ let network = "unknown";
1710
+ const paymentHeader = response.headers.get("PAYMENT-RESPONSE");
1711
+ if (paymentHeader) {
1712
+ try {
1713
+ const decoded = JSON.parse(atob(paymentHeader));
1714
+ network = decoded.network || network;
1715
+ } catch {
1716
+ }
1717
+ }
1718
+ ledger.push({ amount: pendingAmount, domain, network, timestamp: Date.now() });
1719
+ pendingAmount = 0;
1720
+ }
1721
+ return response;
1722
+ });
1723
+ return {
1724
+ fetch: budgetFetch,
1725
+ get spent() {
1726
+ return `$${getSpent().toFixed(2)}`;
1727
+ },
1728
+ get remaining() {
1729
+ return `$${(totalBudget - getSpent()).toFixed(2)}`;
1730
+ },
1731
+ get payments() {
1732
+ return ledger.length;
1733
+ },
1734
+ get spentAmount() {
1735
+ return getSpent();
1736
+ },
1737
+ get remainingAmount() {
1738
+ return totalBudget - getSpent();
1739
+ },
1740
+ get ledger() {
1741
+ return ledger;
1742
+ },
1743
+ get hourlySpend() {
1744
+ return getHourlySpend();
1745
+ },
1746
+ reset() {
1747
+ ledger = [];
1748
+ }
1749
+ };
1750
+ }
1751
+
1193
1752
  // src/client/sponsored-access.ts
1194
1753
  function getSponsoredAccessInfo(response) {
1195
1754
  const receipt = getPaymentReceipt(response);
@@ -1219,6 +1778,7 @@ async function fireImpressionBeacon(response) {
1219
1778
  SOLANA_MAINNET,
1220
1779
  USDC_MINT,
1221
1780
  X402Error,
1781
+ createBudgetAccount,
1222
1782
  createEvmAdapter,
1223
1783
  createEvmKeypairWallet,
1224
1784
  createKeypairWallet,
@@ -1230,6 +1790,6 @@ async function fireImpressionBeacon(response) {
1230
1790
  getSponsoredRecommendations,
1231
1791
  isEvmKeypairWallet,
1232
1792
  isKeypairWallet,
1793
+ searchAPIs,
1233
1794
  wrapFetch
1234
1795
  });
1235
- //# sourceMappingURL=index.cjs.map