@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
@@ -222,16 +222,40 @@ function createSolanaAdapter(config) {
222
222
  }
223
223
 
224
224
  // src/adapters/evm.ts
225
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
226
+ var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
227
+ var PERMIT2_WITNESS_TYPES = {
228
+ PermitWitnessTransferFrom: [
229
+ { name: "permitted", type: "TokenPermissions" },
230
+ { name: "spender", type: "address" },
231
+ { name: "nonce", type: "uint256" },
232
+ { name: "deadline", type: "uint256" },
233
+ { name: "witness", type: "Witness" }
234
+ ],
235
+ TokenPermissions: [
236
+ { name: "token", type: "address" },
237
+ { name: "amount", type: "uint256" }
238
+ ],
239
+ Witness: [
240
+ { name: "to", type: "address" },
241
+ { name: "validAfter", type: "uint256" }
242
+ ]
243
+ };
244
+ var MAX_UINT256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
225
245
  var BASE_MAINNET = "eip155:8453";
226
246
  var BASE_SEPOLIA = "eip155:84532";
227
247
  var ARBITRUM_ONE = "eip155:42161";
228
248
  var POLYGON = "eip155:137";
229
249
  var OPTIMISM = "eip155:10";
230
250
  var AVALANCHE = "eip155:43114";
251
+ var BSC_MAINNET = "eip155:56";
231
252
  var SKALE_BASE = "eip155:1187947933";
232
253
  var SKALE_BASE_SEPOLIA = "eip155:324705682";
233
254
  var ETHEREUM_MAINNET = "eip155:1";
255
+ var BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
256
+ var BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
234
257
  var CHAIN_IDS = {
258
+ [BSC_MAINNET]: 56,
235
259
  [BASE_MAINNET]: 8453,
236
260
  [BASE_SEPOLIA]: 84532,
237
261
  [ARBITRUM_ONE]: 42161,
@@ -243,6 +267,7 @@ var CHAIN_IDS = {
243
267
  [ETHEREUM_MAINNET]: 1
244
268
  };
245
269
  var DEFAULT_RPC_URLS2 = {
270
+ [BSC_MAINNET]: "https://bsc-dataseed1.binance.org",
246
271
  [BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
247
272
  [BASE_SEPOLIA]: "https://sepolia.base.org",
248
273
  [ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
@@ -254,6 +279,7 @@ var DEFAULT_RPC_URLS2 = {
254
279
  [ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
255
280
  };
256
281
  var USDC_ADDRESSES = {
282
+ [BSC_MAINNET]: BSC_USDC,
257
283
  [BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
258
284
  [BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
259
285
  [ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
@@ -264,6 +290,10 @@ var USDC_ADDRESSES = {
264
290
  [SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
265
291
  [ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
266
292
  };
293
+ var BSC_STABLECOIN_ADDRESSES = {
294
+ [BSC_USDT]: { symbol: "USDT", decimals: 18 },
295
+ [BSC_USDC]: { symbol: "USDC", decimals: 18 }
296
+ };
267
297
  function isEvmWallet(wallet) {
268
298
  if (!wallet || typeof wallet !== "object") return false;
269
299
  const w = wallet;
@@ -271,7 +301,7 @@ function isEvmWallet(wallet) {
271
301
  }
272
302
  var EvmAdapter = class {
273
303
  name = "EVM";
274
- networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
304
+ networks = [BSC_MAINNET, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
275
305
  config;
276
306
  log;
277
307
  constructor(config = {}) {
@@ -282,6 +312,7 @@ var EvmAdapter = class {
282
312
  canHandle(network) {
283
313
  if (this.networks.includes(network)) return true;
284
314
  if (network === "base") return true;
315
+ if (network === "bsc") return true;
285
316
  if (network === "ethereum") return true;
286
317
  if (network === "arbitrum") return true;
287
318
  if (network.startsWith("eip155:")) return true;
@@ -295,6 +326,7 @@ var EvmAdapter = class {
295
326
  return DEFAULT_RPC_URLS2[network];
296
327
  }
297
328
  if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
329
+ if (network === "bsc") return DEFAULT_RPC_URLS2[BSC_MAINNET];
298
330
  if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
299
331
  if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
300
332
  return DEFAULT_RPC_URLS2[BASE_MAINNET];
@@ -314,6 +346,7 @@ var EvmAdapter = class {
314
346
  return parseInt(chainIdStr, 10);
315
347
  }
316
348
  if (network === "base") return 8453;
349
+ if (network === "bsc") return 56;
317
350
  if (network === "ethereum") return 1;
318
351
  if (network === "arbitrum") return 42161;
319
352
  return 8453;
@@ -363,13 +396,19 @@ var EvmAdapter = class {
363
396
  const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
364
397
  return selector + paddedAddress;
365
398
  }
366
- async buildTransaction(accept, wallet, _rpcUrl) {
399
+ async buildTransaction(accept, wallet, rpcUrl) {
367
400
  if (!isEvmWallet(wallet)) {
368
401
  throw new Error("Invalid EVM wallet");
369
402
  }
370
403
  if (!wallet.address) {
371
404
  throw new Error("Wallet not connected");
372
405
  }
406
+ if (accept.scheme === "exact-approval") {
407
+ return this.buildApprovalTransaction(accept, wallet, rpcUrl);
408
+ }
409
+ if (accept.extra?.assetTransferMethod === "permit2") {
410
+ return this.buildPermit2Transaction(accept, wallet, rpcUrl);
411
+ }
373
412
  const { payTo, asset, extra } = accept;
374
413
  const amount = accept.amount ?? accept.maxAmountRequired;
375
414
  if (!amount) {
@@ -440,6 +479,325 @@ var EvmAdapter = class {
440
479
  signature
441
480
  };
442
481
  }
482
+ // ===========================================================================
483
+ // exact-approval: BSC and other chains without EIP-3009
484
+ // ===========================================================================
485
+ /**
486
+ * Build a payment transaction for chains that use the approval-based scheme.
487
+ * The facilitator's /supported response provides the EIP-712 domain and types
488
+ * in accept.extra, so the client doesn't hardcode any contract addresses.
489
+ */
490
+ async buildApprovalTransaction(accept, wallet, rpcUrl) {
491
+ const { payTo, asset, extra } = accept;
492
+ const amount = accept.amount ?? accept.maxAmountRequired;
493
+ if (!amount) {
494
+ throw new Error("Missing amount in payment requirements");
495
+ }
496
+ const facilitatorContract = extra?.facilitatorContract;
497
+ if (!facilitatorContract) {
498
+ throw new Error(
499
+ "exact-approval scheme requires extra.facilitatorContract from the facilitator. The /supported endpoint should provide this."
500
+ );
501
+ }
502
+ if (!wallet.signTypedData) {
503
+ throw new Error("Wallet does not support signTypedData (EIP-712)");
504
+ }
505
+ this.log("Building approval-based transaction:", {
506
+ from: wallet.address,
507
+ to: payTo,
508
+ amount,
509
+ asset,
510
+ network: accept.network,
511
+ facilitatorContract
512
+ });
513
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
514
+ const fee = extra?.fee ?? "0";
515
+ const totalNeeded = BigInt(amount) + BigInt(fee);
516
+ const currentAllowance = await this.readAllowance(url, asset, wallet.address, facilitatorContract);
517
+ if (currentAllowance < totalNeeded) {
518
+ if (!wallet.sendTransaction) {
519
+ throw new Error(
520
+ "BSC payments require a wallet that supports sendTransaction for the one-time token approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
521
+ );
522
+ }
523
+ const approvalAmount = this.calculateApprovalAmount(amount, fee, extra?.approvalStrategy);
524
+ this.log(`Approving ${approvalAmount} for ${facilitatorContract} (current allowance: ${currentAllowance})`);
525
+ const approveTxHash = await wallet.sendTransaction({
526
+ to: asset,
527
+ data: this.encodeApprove(facilitatorContract, approvalAmount),
528
+ value: 0n
529
+ });
530
+ this.log(`Approval tx sent: ${approveTxHash}`);
531
+ await this.waitForReceipt(url, approveTxHash);
532
+ this.log("Approval confirmed");
533
+ } else {
534
+ this.log("Sufficient allowance, skipping approval");
535
+ }
536
+ const nonceBytes = new Uint8Array(16);
537
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
538
+ const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n).toString();
539
+ const paymentIdBytes = new Uint8Array(32);
540
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(paymentIdBytes);
541
+ const paymentId = "0x" + [...paymentIdBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
542
+ const now = Math.floor(Date.now() / 1e3);
543
+ const deadline = now + (accept.maxTimeoutSeconds || 300);
544
+ const eip712Domain = extra?.eip712Domain;
545
+ const domain = eip712Domain ? {
546
+ name: eip712Domain.name,
547
+ version: eip712Domain.version,
548
+ chainId: BigInt(eip712Domain.chainId),
549
+ verifyingContract: eip712Domain.verifyingContract
550
+ } : {
551
+ name: "DexterBSCFacilitator",
552
+ version: "1",
553
+ chainId: BigInt(this.getChainId(accept.network)),
554
+ verifyingContract: facilitatorContract
555
+ };
556
+ const types = extra?.eip712Types ?? {
557
+ Payment: [
558
+ { name: "from", type: "address" },
559
+ { name: "to", type: "address" },
560
+ { name: "token", type: "address" },
561
+ { name: "amount", type: "uint256" },
562
+ { name: "fee", type: "uint256" },
563
+ { name: "nonce", type: "uint256" },
564
+ { name: "deadline", type: "uint256" },
565
+ { name: "paymentId", type: "bytes32" }
566
+ ]
567
+ };
568
+ const message = {
569
+ from: wallet.address,
570
+ to: payTo,
571
+ token: asset,
572
+ amount: BigInt(amount),
573
+ fee: BigInt(fee),
574
+ nonce: BigInt(nonce),
575
+ deadline: BigInt(deadline),
576
+ paymentId
577
+ };
578
+ const signature = await wallet.signTypedData({
579
+ domain,
580
+ types,
581
+ primaryType: "Payment",
582
+ message
583
+ });
584
+ this.log("EIP-712 Payment signature obtained");
585
+ const payload = {
586
+ from: wallet.address,
587
+ to: payTo,
588
+ token: asset,
589
+ amount,
590
+ fee,
591
+ nonce,
592
+ deadline,
593
+ paymentId,
594
+ signature
595
+ };
596
+ return {
597
+ serialized: JSON.stringify(payload),
598
+ signature
599
+ };
600
+ }
601
+ // ===========================================================================
602
+ // Permit2: Universal ERC-20 payments via Uniswap's Permit2 contract
603
+ // ===========================================================================
604
+ /**
605
+ * Build a Permit2 payment transaction. Used when the facilitator signals
606
+ * assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
607
+ *
608
+ * Flow:
609
+ * 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
610
+ * 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
611
+ * 3. Return { permit2Authorization, signature } payload for the facilitator.
612
+ */
613
+ async buildPermit2Transaction(accept, wallet, rpcUrl) {
614
+ const { payTo, asset } = accept;
615
+ const amount = accept.amount ?? accept.maxAmountRequired;
616
+ if (!amount) {
617
+ throw new Error("Missing amount in payment requirements");
618
+ }
619
+ if (!wallet.signTypedData) {
620
+ throw new Error("Wallet does not support signTypedData (EIP-712)");
621
+ }
622
+ this.log("Building Permit2 transaction:", {
623
+ from: wallet.address,
624
+ to: payTo,
625
+ amount,
626
+ asset,
627
+ network: accept.network
628
+ });
629
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
630
+ const currentAllowance = await this.readAllowance(url, asset, wallet.address, PERMIT2_ADDRESS);
631
+ if (currentAllowance < BigInt(amount)) {
632
+ if (!wallet.sendTransaction) {
633
+ throw new Error(
634
+ "Permit2 payments require a wallet that supports sendTransaction for the one-time Permit2 approval. Use createEvmKeypairWallet() or a browser wallet with transaction support."
635
+ );
636
+ }
637
+ this.log(`Approving Permit2 for ${asset} (current allowance: ${currentAllowance})`);
638
+ const approveTxHash = await wallet.sendTransaction({
639
+ to: asset,
640
+ data: this.encodeApprove(PERMIT2_ADDRESS, MAX_UINT256),
641
+ value: 0n
642
+ });
643
+ this.log(`Permit2 approval tx sent: ${approveTxHash}`);
644
+ await this.waitForReceipt(url, approveTxHash);
645
+ this.log("Permit2 approval confirmed");
646
+ } else {
647
+ this.log("Sufficient Permit2 allowance, skipping approval");
648
+ }
649
+ const nonceBytes = new Uint8Array(32);
650
+ (globalThis.crypto ?? (await import("crypto")).webcrypto).getRandomValues(nonceBytes);
651
+ const nonce = [...nonceBytes].reduce((acc, b) => acc * 256n + BigInt(b), 0n);
652
+ const now = Math.floor(Date.now() / 1e3);
653
+ const validAfter = now - 600;
654
+ const deadline = now + (accept.maxTimeoutSeconds || 300);
655
+ const chainId = this.getChainId(accept.network);
656
+ const domain = {
657
+ name: "Permit2",
658
+ chainId: BigInt(chainId),
659
+ verifyingContract: PERMIT2_ADDRESS
660
+ };
661
+ const message = {
662
+ permitted: {
663
+ token: asset,
664
+ amount: BigInt(amount)
665
+ },
666
+ spender: X402_EXACT_PERMIT2_PROXY,
667
+ nonce,
668
+ deadline: BigInt(deadline),
669
+ witness: {
670
+ to: payTo,
671
+ validAfter: BigInt(validAfter)
672
+ }
673
+ };
674
+ const signature = await wallet.signTypedData({
675
+ domain,
676
+ types: PERMIT2_WITNESS_TYPES,
677
+ primaryType: "PermitWitnessTransferFrom",
678
+ message
679
+ });
680
+ this.log("Permit2 PermitWitnessTransferFrom signature obtained");
681
+ const payload = {
682
+ signature,
683
+ permit2Authorization: {
684
+ from: wallet.address,
685
+ permitted: {
686
+ token: asset,
687
+ amount
688
+ },
689
+ spender: X402_EXACT_PERMIT2_PROXY,
690
+ nonce: nonce.toString(),
691
+ deadline: String(deadline),
692
+ witness: {
693
+ to: payTo,
694
+ validAfter: String(validAfter)
695
+ }
696
+ }
697
+ };
698
+ return {
699
+ serialized: JSON.stringify(payload),
700
+ signature
701
+ };
702
+ }
703
+ /**
704
+ * Read ERC-20 allowance via raw eth_call (no viem dependency needed).
705
+ */
706
+ async readAllowance(rpcUrl, token, owner, spender) {
707
+ const selector = "0xdd62ed3e";
708
+ const paddedOwner = owner.slice(2).toLowerCase().padStart(64, "0");
709
+ const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
710
+ const data = selector + paddedOwner + paddedSpender;
711
+ try {
712
+ const response = await fetch(rpcUrl, {
713
+ method: "POST",
714
+ headers: { "Content-Type": "application/json" },
715
+ body: JSON.stringify({
716
+ jsonrpc: "2.0",
717
+ id: 1,
718
+ method: "eth_call",
719
+ params: [{ to: token, data }, "latest"]
720
+ })
721
+ });
722
+ const result = await response.json();
723
+ if (result.error || !result.result || result.result === "0x") return 0n;
724
+ return BigInt(result.result);
725
+ } catch {
726
+ return 0n;
727
+ }
728
+ }
729
+ /**
730
+ * Encode ERC-20 approve(address,uint256) calldata.
731
+ */
732
+ encodeApprove(spender, amount) {
733
+ const selector = "0x095ea7b3";
734
+ const paddedSpender = spender.slice(2).toLowerCase().padStart(64, "0");
735
+ const paddedAmount = amount.toString(16).padStart(64, "0");
736
+ return selector + paddedSpender + paddedAmount;
737
+ }
738
+ /**
739
+ * Wait for a transaction receipt by polling eth_getTransactionReceipt.
740
+ */
741
+ async waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
742
+ const start = Date.now();
743
+ while (Date.now() - start < timeoutMs) {
744
+ try {
745
+ const response = await fetch(rpcUrl, {
746
+ method: "POST",
747
+ headers: { "Content-Type": "application/json" },
748
+ body: JSON.stringify({
749
+ jsonrpc: "2.0",
750
+ id: 1,
751
+ method: "eth_getTransactionReceipt",
752
+ params: [txHash]
753
+ })
754
+ });
755
+ const result = await response.json();
756
+ if (result.result) {
757
+ if (result.result.status === "0x0") {
758
+ throw new Error(`Approval transaction reverted: ${txHash}`);
759
+ }
760
+ return;
761
+ }
762
+ } catch (err) {
763
+ if (err instanceof Error && err.message.includes("reverted")) throw err;
764
+ }
765
+ await new Promise((r) => setTimeout(r, 2e3));
766
+ }
767
+ throw new Error(`Approval transaction receipt timeout after ${timeoutMs}ms: ${txHash}`);
768
+ }
769
+ /**
770
+ * Calculate how much to approve based on the facilitator's approval strategy.
771
+ * Buffered approvals reduce the number of on-chain approval txs for micropayments.
772
+ */
773
+ calculateApprovalAmount(paymentAmount, fee, strategy) {
774
+ const total = BigInt(paymentAmount) + BigInt(fee);
775
+ if (!strategy || strategy.mode === "exact") {
776
+ return total;
777
+ }
778
+ const multiple = BigInt(strategy.defaultMultiple ?? 10);
779
+ const buffered = total * multiple;
780
+ if (strategy.maxCapUsd) {
781
+ const decimals = this.inferDecimals(paymentAmount);
782
+ const maxCap = BigInt(Math.floor(strategy.maxCapUsd * Math.pow(10, decimals)));
783
+ if (buffered > maxCap) return maxCap;
784
+ }
785
+ if (strategy.exactAboveUsd) {
786
+ const decimals = this.inferDecimals(paymentAmount);
787
+ const threshold = BigInt(Math.floor(strategy.exactAboveUsd * Math.pow(10, decimals)));
788
+ if (BigInt(paymentAmount) > threshold) return total;
789
+ }
790
+ return buffered;
791
+ }
792
+ /**
793
+ * Infer token decimals from payment amount magnitude.
794
+ * BSC stablecoins use 18 decimals, all others use 6.
795
+ * A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
796
+ * If the amount has > 12 digits, it's almost certainly 18 decimals.
797
+ */
798
+ inferDecimals(amount) {
799
+ return amount.length > 12 ? 18 : 6;
800
+ }
443
801
  };
444
802
  function createEvmAdapter(config) {
445
803
  return new EvmAdapter(config);
@@ -453,6 +811,9 @@ function isKnownUSDC(asset) {
453
811
  for (const addr of Object.values(USDC_ADDRESSES)) {
454
812
  if (addr.toLowerCase() === lc) return true;
455
813
  }
814
+ for (const addr of Object.keys(BSC_STABLECOIN_ADDRESSES)) {
815
+ if (addr.toLowerCase() === lc) return true;
816
+ }
456
817
  return false;
457
818
  }
458
819
 
@@ -472,10 +833,33 @@ function createX402Client(config) {
472
833
  fetch: customFetch = globalThis.fetch,
473
834
  verbose = false,
474
835
  accessPass: accessPassConfig,
475
- onPaymentRequired
836
+ onPaymentRequired,
837
+ maxRetries = 0,
838
+ retryDelayMs = 500
476
839
  } = config;
477
840
  const log = verbose ? console.log.bind(console, "[x402]") : () => {
478
841
  };
842
+ async function fetchWithRetry(input, init) {
843
+ let lastError;
844
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
845
+ try {
846
+ const response = await customFetch(input, init);
847
+ if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
848
+ log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
849
+ await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
850
+ continue;
851
+ }
852
+ return response;
853
+ } catch (err) {
854
+ lastError = err;
855
+ if (attempt < maxRetries) {
856
+ log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
857
+ await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
858
+ }
859
+ }
860
+ }
861
+ throw lastError;
862
+ }
479
863
  const passCache = /* @__PURE__ */ new Map();
480
864
  function getCachedPass(url) {
481
865
  try {
@@ -538,6 +922,19 @@ function createX402Client(config) {
538
922
  }
539
923
  return candidates[0];
540
924
  }
925
+ function getChainDisplayName(network, adapterName) {
926
+ const names = {
927
+ "eip155:56": "BSC",
928
+ "eip155:8453": "Base",
929
+ "eip155:84532": "Base Sepolia",
930
+ "eip155:42161": "Arbitrum",
931
+ "eip155:137": "Polygon",
932
+ "eip155:10": "Optimism",
933
+ "eip155:43114": "Avalanche",
934
+ "eip155:1": "Ethereum"
935
+ };
936
+ return names[network] || adapterName;
937
+ }
541
938
  function getRpcUrl(network, adapter) {
542
939
  return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
543
940
  }
@@ -675,7 +1072,7 @@ function createX402Client(config) {
675
1072
  }
676
1073
  }
677
1074
  }
678
- const response = await customFetch(input, init);
1075
+ const response = await fetchWithRetry(input, init);
679
1076
  if (response.status !== 402) {
680
1077
  return response;
681
1078
  }
@@ -752,10 +1149,10 @@ function createX402Client(config) {
752
1149
  const balance = await adapter.getBalance(accept, wallet, rpcUrl);
753
1150
  const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
754
1151
  if (balance < requiredAmount) {
755
- const network = adapter.name === "EVM" ? "Base" : "Solana";
1152
+ const chainName = getChainDisplayName(accept.network, adapter.name);
756
1153
  throw new X402Error(
757
1154
  "insufficient_balance",
758
- `Insufficient USDC balance on ${network}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
1155
+ `Insufficient balance on ${chainName}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
759
1156
  );
760
1157
  }
761
1158
  log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
@@ -810,7 +1207,7 @@ function createX402Client(config) {
810
1207
  };
811
1208
  const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
812
1209
  log("Retrying request with payment...");
813
- const retryResponse = await customFetch(input, {
1210
+ const retryResponse = await fetchWithRetry(input, {
814
1211
  ...init,
815
1212
  headers: {
816
1213
  ...init?.headers || {},
@@ -1319,4 +1716,3 @@ export {
1319
1716
  useAccessPass,
1320
1717
  useX402Payment
1321
1718
  };
1322
- //# sourceMappingURL=index.js.map
@@ -196,11 +196,11 @@ var FacilitatorClient = class {
196
196
  async getFeePayer(network) {
197
197
  const supported = await this.getSupported();
198
198
  const kind = supported.kinds.find(
199
- (k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
199
+ (k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
200
200
  );
201
201
  if (!kind) {
202
202
  throw new Error(
203
- `Facilitator does not support network "${network}" with scheme "exact"`
203
+ `Facilitator does not support network "${network}" with a recognized scheme`
204
204
  );
205
205
  }
206
206
  return kind.extra?.feePayer;
@@ -211,7 +211,7 @@ var FacilitatorClient = class {
211
211
  async getNetworkExtra(network) {
212
212
  const supported = await this.getSupported();
213
213
  const kind = supported.kinds.find(
214
- (k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
214
+ (k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
215
215
  );
216
216
  return kind?.extra;
217
217
  }
@@ -2162,4 +2162,3 @@ var import_x402_ads_types = require("@dexterai/x402-ads-types");
2162
2162
  x402BrowserSupport,
2163
2163
  x402Middleware
2164
2164
  });
2165
- //# sourceMappingURL=index.cjs.map
@@ -1,5 +1,5 @@
1
- import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-CjLMR7qs.cjs';
2
- export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-CjLMR7qs.cjs';
1
+ import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-_iT11DL0.cjs';
2
+ export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-_iT11DL0.cjs';
3
3
  import { Request, RequestHandler } from 'express';
4
4
  import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
5
5
  export { SPONSORED_ACCESS_EXTENSION_KEY, SponsoredAccessClientConsent, SponsoredAccessPaymentRequiredInfo, SponsoredAccessSettlementInfo, SponsoredRecommendation } from '@dexterai/x402-ads-types';
@@ -1,5 +1,5 @@
1
- import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-CjLMR7qs.js';
2
- export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-CjLMR7qs.js';
1
+ import { P as PaymentAccept, V as VerifyResponse, S as SettleResponse, c as PayToProvider, d as PaymentRequired } from '../types-_iT11DL0.js';
2
+ export { g as AccessPassClaims, A as AccessPassClientConfig, b as AccessPassInfo, a as AccessPassTier, B as BASE_MAINNET_NETWORK, D as DEXTER_FACILITATOR_URL, e as PayToContext, f as PayToProviderDefaults, h as SOLANA_MAINNET_NETWORK, i as USDC_BASE, U as USDC_MINT } from '../types-_iT11DL0.js';
3
3
  import { Request, RequestHandler } from 'express';
4
4
  import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
5
5
  export { SPONSORED_ACCESS_EXTENSION_KEY, SponsoredAccessClientConsent, SponsoredAccessPaymentRequiredInfo, SponsoredAccessSettlementInfo, SponsoredRecommendation } from '@dexterai/x402-ads-types';
@@ -127,11 +127,11 @@ var FacilitatorClient = class {
127
127
  async getFeePayer(network) {
128
128
  const supported = await this.getSupported();
129
129
  const kind = supported.kinds.find(
130
- (k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
130
+ (k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
131
131
  );
132
132
  if (!kind) {
133
133
  throw new Error(
134
- `Facilitator does not support network "${network}" with scheme "exact"`
134
+ `Facilitator does not support network "${network}" with a recognized scheme`
135
135
  );
136
136
  }
137
137
  return kind.extra?.feePayer;
@@ -142,7 +142,7 @@ var FacilitatorClient = class {
142
142
  async getNetworkExtra(network) {
143
143
  const supported = await this.getSupported();
144
144
  const kind = supported.kinds.find(
145
- (k) => k.x402Version === 2 && k.scheme === "exact" && k.network === network
145
+ (k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
146
146
  );
147
147
  return kind?.extra;
148
148
  }
@@ -2092,4 +2092,3 @@ export {
2092
2092
  x402BrowserSupport,
2093
2093
  x402Middleware
2094
2094
  };
2095
- //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
- import { C as ChainAdapter, W as WalletSet } from './types-BIHhO2-I.js';
2
- import { A as AccessPassClientConfig, P as PaymentAccept } from './types-CjLMR7qs.js';
1
+ import { C as ChainAdapter, W as WalletSet } from './types-D1u7iu8n.cjs';
2
+ import { A as AccessPassClientConfig, P as PaymentAccept } from './types-_iT11DL0.cjs';
3
3
  import { SponsoredRecommendation, SponsoredAccessSettlementInfo } from '@dexterai/x402-ads-types';
4
4
 
5
5
  /**
@@ -112,6 +112,17 @@ interface X402ClientConfig {
112
112
  * ```
113
113
  */
114
114
  onPaymentRequired?: (requirements: PaymentAccept) => boolean | Promise<boolean>;
115
+ /**
116
+ * Maximum retry attempts for transient failures (network errors, 502/503).
117
+ * Does not cause double payments — EIP-3009 nonces prevent replay.
118
+ * @default 0 (no retry)
119
+ */
120
+ maxRetries?: number;
121
+ /**
122
+ * Base delay between retries in milliseconds (doubles each attempt).
123
+ * @default 500
124
+ */
125
+ retryDelayMs?: number;
115
126
  }
116
127
  /**
117
128
  * x402 Client interface
@@ -1,5 +1,5 @@
1
- import { C as ChainAdapter, W as WalletSet } from './types-CfKflCZO.cjs';
2
- import { A as AccessPassClientConfig, P as PaymentAccept } from './types-CjLMR7qs.cjs';
1
+ import { C as ChainAdapter, W as WalletSet } from './types-YQlJI5E3.js';
2
+ import { A as AccessPassClientConfig, P as PaymentAccept } from './types-_iT11DL0.js';
3
3
  import { SponsoredRecommendation, SponsoredAccessSettlementInfo } from '@dexterai/x402-ads-types';
4
4
 
5
5
  /**
@@ -112,6 +112,17 @@ interface X402ClientConfig {
112
112
  * ```
113
113
  */
114
114
  onPaymentRequired?: (requirements: PaymentAccept) => boolean | Promise<boolean>;
115
+ /**
116
+ * Maximum retry attempts for transient failures (network errors, 502/503).
117
+ * Does not cause double payments — EIP-3009 nonces prevent replay.
118
+ * @default 0 (no retry)
119
+ */
120
+ maxRetries?: number;
121
+ /**
122
+ * Base delay between retries in milliseconds (doubles each attempt).
123
+ * @default 500
124
+ */
125
+ retryDelayMs?: number;
115
126
  }
116
127
  /**
117
128
  * x402 Client interface