@dexterai/x402 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
 
@@ -561,6 +922,19 @@ function createX402Client(config) {
561
922
  }
562
923
  return candidates[0];
563
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
+ }
564
938
  function getRpcUrl(network, adapter) {
565
939
  return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
566
940
  }
@@ -775,10 +1149,10 @@ function createX402Client(config) {
775
1149
  const balance = await adapter.getBalance(accept, wallet, rpcUrl);
776
1150
  const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
777
1151
  if (balance < requiredAmount) {
778
- const network = adapter.name === "EVM" ? "Base" : "Solana";
1152
+ const chainName = getChainDisplayName(accept.network, adapter.name);
779
1153
  throw new X402Error(
780
1154
  "insufficient_balance",
781
- `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)}`
782
1156
  );
783
1157
  }
784
1158
  log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
@@ -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
  }
@@ -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
  }
@@ -1,5 +1,5 @@
1
- import { C as ChainAdapter, W as WalletSet } from './types-DWhpiOBD.cjs';
2
- import { A as AccessPassClientConfig, P as PaymentAccept } from './types-CjLMR7qs.cjs';
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
  /**
@@ -1,5 +1,5 @@
1
- import { C as ChainAdapter, W as WalletSet } from './types-D1TGACsL.js';
2
- import { A as AccessPassClientConfig, P as PaymentAccept } from './types-CjLMR7qs.js';
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
  /**
@@ -1,4 +1,4 @@
1
- import { P as PaymentAccept } from './types-CjLMR7qs.cjs';
1
+ import { P as PaymentAccept } from './types-_iT11DL0.cjs';
2
2
 
3
3
  /**
4
4
  * EVM Chain Adapter
@@ -7,6 +7,11 @@ import { P as PaymentAccept } from './types-CjLMR7qs.cjs';
7
7
  * Uses EIP-712 typed data signing for x402 v2 payments.
8
8
  */
9
9
 
10
+ /**
11
+ * Permit2 constants (Uniswap canonical deployment, same address on every EVM chain)
12
+ */
13
+ declare const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
14
+ declare const X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
10
15
  /**
11
16
  * CAIP-2 network identifiers for EVM chains.
12
17
  * Mirrors dexter-facilitator/src/config/chains.ts — update both when adding chains.
@@ -17,15 +22,29 @@ declare const ARBITRUM_ONE = "eip155:42161";
17
22
  declare const POLYGON = "eip155:137";
18
23
  declare const OPTIMISM = "eip155:10";
19
24
  declare const AVALANCHE = "eip155:43114";
25
+ declare const BSC_MAINNET = "eip155:56";
20
26
  declare const SKALE_BASE = "eip155:1187947933";
21
27
  declare const SKALE_BASE_SEPOLIA = "eip155:324705682";
22
28
  /** @deprecated Not supported by the Dexter facilitator. Use BASE_MAINNET for EVM payments. */
23
29
  declare const ETHEREUM_MAINNET = "eip155:1";
30
+ /**
31
+ * BSC stablecoin addresses (18 decimals — unlike 6 on every other chain)
32
+ */
33
+ declare const BSC_USDT = "0x55d398326f99059fF775485246999027B3197955";
34
+ declare const BSC_USDC = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
24
35
  /**
25
36
  * USDC contract addresses by chain.
26
37
  * Source of truth: dexter-facilitator/src/config/chains.ts
27
38
  */
28
39
  declare const USDC_ADDRESSES: Record<string, string>;
40
+ /**
41
+ * Known BSC stablecoin addresses (for isKnownStablecoin checks).
42
+ * Both use 18 decimals on BSC, unlike the 6 decimals on all other chains.
43
+ */
44
+ declare const BSC_STABLECOIN_ADDRESSES: Record<string, {
45
+ symbol: string;
46
+ decimals: number;
47
+ }>;
29
48
  /**
30
49
  * EVM wallet interface (compatible with wagmi, ethers, viem)
31
50
  */
@@ -74,7 +93,47 @@ declare class EvmAdapter implements ChainAdapter {
74
93
  private getChainId;
75
94
  getBalance(accept: PaymentAccept, wallet: unknown, rpcUrl?: string): Promise<number>;
76
95
  private encodeBalanceOf;
77
- buildTransaction(accept: PaymentAccept, wallet: unknown, _rpcUrl?: string): Promise<SignedTransaction>;
96
+ buildTransaction(accept: PaymentAccept, wallet: unknown, rpcUrl?: string): Promise<SignedTransaction>;
97
+ /**
98
+ * Build a payment transaction for chains that use the approval-based scheme.
99
+ * The facilitator's /supported response provides the EIP-712 domain and types
100
+ * in accept.extra, so the client doesn't hardcode any contract addresses.
101
+ */
102
+ private buildApprovalTransaction;
103
+ /**
104
+ * Build a Permit2 payment transaction. Used when the facilitator signals
105
+ * assetTransferMethod: "permit2" in extra (e.g., BSC where EIP-3009 is unavailable).
106
+ *
107
+ * Flow:
108
+ * 1. Check if token has approved the Permit2 contract. If not, approve(Permit2, maxUint256).
109
+ * 2. Sign EIP-712 PermitWitnessTransferFrom against the Permit2 contract.
110
+ * 3. Return { permit2Authorization, signature } payload for the facilitator.
111
+ */
112
+ private buildPermit2Transaction;
113
+ /**
114
+ * Read ERC-20 allowance via raw eth_call (no viem dependency needed).
115
+ */
116
+ private readAllowance;
117
+ /**
118
+ * Encode ERC-20 approve(address,uint256) calldata.
119
+ */
120
+ private encodeApprove;
121
+ /**
122
+ * Wait for a transaction receipt by polling eth_getTransactionReceipt.
123
+ */
124
+ private waitForReceipt;
125
+ /**
126
+ * Calculate how much to approve based on the facilitator's approval strategy.
127
+ * Buffered approvals reduce the number of on-chain approval txs for micropayments.
128
+ */
129
+ private calculateApprovalAmount;
130
+ /**
131
+ * Infer token decimals from payment amount magnitude.
132
+ * BSC stablecoins use 18 decimals, all others use 6.
133
+ * A $1 payment is 1000000 (6 dec) or 1000000000000000000 (18 dec).
134
+ * If the amount has > 12 digits, it's almost certainly 18 decimals.
135
+ */
136
+ private inferDecimals;
78
137
  }
79
138
  /**
80
139
  * Create an EVM adapter instance
@@ -242,4 +301,4 @@ interface BalanceInfo {
242
301
  asset: string;
243
302
  }
244
303
 
245
- export { type AdapterConfig as A, BASE_MAINNET as B, type ChainAdapter as C, type EvmWallet as E, type GenericWallet as G, OPTIMISM as O, POLYGON as P, type SolanaWallet as S, USDC_ADDRESSES as U, type WalletSet as W, createEvmAdapter as a, SOLANA_MAINNET as b, createSolanaAdapter as c, type BalanceInfo as d, SolanaAdapter as e, EvmAdapter as f, type SignedTransaction as g, SOLANA_DEVNET as h, isSolanaWallet as i, SOLANA_TESTNET as j, isEvmWallet as k, BASE_SEPOLIA as l, ARBITRUM_ONE as m, AVALANCHE as n, SKALE_BASE as o, SKALE_BASE_SEPOLIA as p, ETHEREUM_MAINNET as q };
304
+ export { type AdapterConfig as A, BASE_MAINNET as B, type ChainAdapter as C, type EvmWallet as E, type GenericWallet as G, OPTIMISM as O, PERMIT2_ADDRESS as P, type SolanaWallet as S, USDC_ADDRESSES as U, type WalletSet as W, X402_EXACT_PERMIT2_PROXY as X, createEvmAdapter as a, SOLANA_MAINNET as b, createSolanaAdapter as c, type BalanceInfo as d, SolanaAdapter as e, EvmAdapter as f, type SignedTransaction as g, SOLANA_DEVNET as h, isSolanaWallet as i, SOLANA_TESTNET as j, isEvmWallet as k, BSC_MAINNET as l, BSC_USDT as m, BSC_USDC as n, BSC_STABLECOIN_ADDRESSES as o, BASE_SEPOLIA as p, ARBITRUM_ONE as q, POLYGON as r, AVALANCHE as s, SKALE_BASE as t, SKALE_BASE_SEPOLIA as u, ETHEREUM_MAINNET as v };