@dexterai/x402 1.9.1 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,10 +77,22 @@ The simplest way to make x402 payments from scripts:
77
77
  ```typescript
78
78
  import { wrapFetch } from '@dexterai/x402/client';
79
79
 
80
+ // Solana
80
81
  const x402Fetch = wrapFetch(fetch, {
81
82
  walletPrivateKey: process.env.SOLANA_PRIVATE_KEY,
82
83
  });
83
84
 
85
+ // EVM (Base, Polygon, Arbitrum, Optimism, Avalanche, SKALE)
86
+ const x402Fetch = wrapFetch(fetch, {
87
+ evmPrivateKey: process.env.EVM_PRIVATE_KEY, // requires: npm install viem
88
+ });
89
+
90
+ // Both — SDK picks the chain with balance
91
+ const x402Fetch = wrapFetch(fetch, {
92
+ walletPrivateKey: process.env.SOLANA_PRIVATE_KEY,
93
+ evmPrivateKey: process.env.EVM_PRIVATE_KEY,
94
+ });
95
+
84
96
  // That's it. 402 responses are handled automatically.
85
97
  const response = await x402Fetch('https://api.example.com/protected');
86
98
  ```
@@ -44,6 +44,7 @@ __export(server_exports, {
44
44
  createDynamicPricing: () => createDynamicPricing,
45
45
  createTokenPricing: () => createTokenPricing,
46
46
  createX402Server: () => createX402Server,
47
+ escapeHtml: () => escapeHtml,
47
48
  estimateCost: () => estimateCost,
48
49
  findModel: () => findModel,
49
50
  formatModelPricing: () => formatModelPricing,
@@ -431,9 +432,104 @@ function createX402Server(config) {
431
432
  };
432
433
  }
433
434
 
435
+ // src/server/stripe-payto.ts
436
+ var stripeProviderNetworks = /* @__PURE__ */ new WeakMap();
437
+ function getStripeProviderNetwork(provider) {
438
+ return stripeProviderNetworks.get(provider);
439
+ }
440
+ var STRIPE_NETWORK_KEYS = {
441
+ "base": "base",
442
+ "base-sepolia": "base_sepolia"
443
+ };
444
+ var CAIP2_NETWORKS = {
445
+ "base": "eip155:8453",
446
+ "base-sepolia": "eip155:84532"
447
+ };
448
+ var USDC_DECIMALS = 6;
449
+ function stripePayTo(secretKeyOrConfig) {
450
+ const config = typeof secretKeyOrConfig === "string" ? { secretKey: secretKeyOrConfig } : secretKeyOrConfig;
451
+ const networkName = config.network ?? "base";
452
+ const stripeNetworkKey = STRIPE_NETWORK_KEYS[networkName] ?? "base";
453
+ const caip2Network = CAIP2_NETWORKS[networkName] ?? "eip155:8453";
454
+ const apiVersion = config.apiVersion ?? "2026-01-28.clover";
455
+ let stripeClient = null;
456
+ async function getStripe() {
457
+ if (stripeClient) return stripeClient;
458
+ try {
459
+ const { default: Stripe } = await import("stripe");
460
+ stripeClient = new Stripe(config.secretKey, {
461
+ apiVersion,
462
+ appInfo: {
463
+ name: "@dexterai/x402",
464
+ url: "https://dexter.cash/sdk"
465
+ }
466
+ });
467
+ return stripeClient;
468
+ } catch {
469
+ throw new Error(
470
+ 'The "stripe" package is required for stripePayTo(). Install it with: npm install stripe'
471
+ );
472
+ }
473
+ }
474
+ const provider = async (context) => {
475
+ if (context.paymentHeader) {
476
+ try {
477
+ const decoded = JSON.parse(
478
+ Buffer.from(context.paymentHeader, "base64").toString()
479
+ );
480
+ const toAddress = decoded.payload?.authorization?.to;
481
+ if (toAddress && typeof toAddress === "string") {
482
+ return toAddress;
483
+ }
484
+ const acceptedPayTo = decoded.accepted?.payTo;
485
+ if (acceptedPayTo && typeof acceptedPayTo === "string") {
486
+ return acceptedPayTo;
487
+ }
488
+ } catch {
489
+ }
490
+ throw new Error(
491
+ "Could not extract deposit address from payment header. Ensure the client is sending a valid x402 PAYMENT-SIGNATURE."
492
+ );
493
+ }
494
+ const stripe = await getStripe();
495
+ const amountAtomic = context.amountAtomic ? parseInt(context.amountAtomic, 10) : 1e4;
496
+ const amountInCents = Math.max(1, Math.round(amountAtomic / Math.pow(10, USDC_DECIMALS - 2)));
497
+ const paymentIntent = await stripe.paymentIntents.create({
498
+ amount: amountInCents,
499
+ currency: "usd",
500
+ payment_method_types: ["crypto"],
501
+ payment_method_data: { type: "crypto" },
502
+ payment_method_options: {
503
+ crypto: { mode: "custom" }
504
+ },
505
+ confirm: true
506
+ });
507
+ const nextAction = paymentIntent.next_action;
508
+ if (!nextAction?.crypto_collect_deposit_details) {
509
+ throw new Error(
510
+ "Stripe PaymentIntent did not return crypto deposit details. Ensure your Stripe account has crypto payins enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto"
511
+ );
512
+ }
513
+ const depositDetails = nextAction.crypto_collect_deposit_details;
514
+ const payToAddress = depositDetails.deposit_addresses?.[stripeNetworkKey]?.address;
515
+ if (!payToAddress) {
516
+ throw new Error(
517
+ `No deposit address found for network "${stripeNetworkKey}". Available networks: ${Object.keys(depositDetails.deposit_addresses || {}).join(", ")}`
518
+ );
519
+ }
520
+ return payToAddress;
521
+ };
522
+ provider._x402Defaults = {
523
+ network: caip2Network,
524
+ facilitatorUrl: "https://x402.dexter.cash"
525
+ };
526
+ stripeProviderNetworks.set(provider, caip2Network);
527
+ return provider;
528
+ }
529
+
434
530
  // src/server/middleware.ts
435
531
  var DEFAULT_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
436
- var USDC_DECIMALS = 6;
532
+ var USDC_DECIMALS2 = 6;
437
533
  function resolvePayToForNetwork(payTo, network) {
438
534
  if (typeof payTo === "string" || typeof payTo === "function") return payTo;
439
535
  if (network in payTo) return payTo[network];
@@ -471,9 +567,9 @@ function x402Middleware(config) {
471
567
  const servers = /* @__PURE__ */ new Map();
472
568
  for (const net of configuredNetworks) {
473
569
  const netPayTo = resolvePayToForNetwork(payTo, net);
474
- if (typeof netPayTo === "function" && netPayTo._stripeNetwork) {
475
- const stripeNet = netPayTo._stripeNetwork;
476
- if (net !== stripeNet) {
570
+ if (typeof netPayTo === "function") {
571
+ const stripeNet = getStripeProviderNetwork(netPayTo);
572
+ if (stripeNet && net !== stripeNet) {
477
573
  throw new Error(
478
574
  `stripePayTo is configured for "${stripeNet}" but middleware includes network "${net}". Stripe only supports Base deposit addresses. Use a static payTo for other chains.`
479
575
  );
@@ -496,7 +592,7 @@ function x402Middleware(config) {
496
592
  const resourceUrl = getResourceUrl?.(req) ?? staticResourceUrl ?? `${req.protocol}://${req.get("host")}${req.originalUrl}`;
497
593
  const requestAmount = getAmount?.(req) ?? amount;
498
594
  const requestDescription = getDescription?.(req) ?? description;
499
- const decimals = asset?.decimals ?? USDC_DECIMALS;
595
+ const decimals = asset?.decimals ?? USDC_DECIMALS2;
500
596
  const amountAtomic = toAtomicUnits(parseFloat(requestAmount), decimals);
501
597
  const requirementsOptions = {
502
598
  amountAtomic,
@@ -615,7 +711,7 @@ function x402Middleware(config) {
615
711
  }
616
712
 
617
713
  // src/server/browser-support.ts
618
- function esc(s) {
714
+ function escapeHtml(s) {
619
715
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
620
716
  }
621
717
  var USDC_ICON_SVG = `<svg width="18" height="18" viewBox="0 0 2000 2000" xmlns="http://www.w3.org/2000/svg"><path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/><path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/><path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`;
@@ -877,22 +973,22 @@ function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config,
877
973
  } catch {
878
974
  }
879
975
  const chainName = network.includes("solana") ? "Solana" : network.includes("eip155") ? "Base" : "";
880
- const endpointSection = config.showEndpoint ? `<div class="endpoint"><code>${esc(method)} ${esc(requestUrl)}</code></div>` : "";
976
+ const endpointSection = config.showEndpoint ? `<div class="endpoint"><code>${escapeHtml(method)} ${escapeHtml(requestUrl)}</code></div>` : "";
881
977
  return `<!DOCTYPE html>
882
978
  <html lang="en">
883
979
  <head>
884
980
  <meta charset="utf-8">
885
981
  <meta name="viewport" content="width=device-width,initial-scale=1">
886
- <title>${esc(config.title)} \u2014 ${esc(price)} USDC</title>
982
+ <title>${escapeHtml(config.title)} \u2014 ${escapeHtml(price)} USDC</title>
887
983
  <style>${DEXTER_STYLES}${PAY_BUTTON_STYLES}</style>
888
984
  </head>
889
985
  <body>
890
986
  <div class="card">
891
987
  <div class="crest">${DEXTER_CREST_SVG}</div>
892
- <h1>${esc(config.title)}</h1>
893
- <p class="desc">${esc(description)}</p>
894
- <div class="price">${USDC_ICON_SVG}<span id="price-value">${esc(price)}</span></div>
895
- <div class="chain">${esc(chainName)}${chainName ? " network" : ""}</div>
988
+ <h1>${escapeHtml(config.title)}</h1>
989
+ <p class="desc">${escapeHtml(description)}</p>
990
+ <div class="price">${USDC_ICON_SVG}<span id="price-value">${escapeHtml(price)}</span></div>
991
+ <div class="chain">${escapeHtml(chainName)}${chainName ? " network" : ""}</div>
896
992
  ${endpointSection}
897
993
 
898
994
  <div id="pay-section" class="pay-section" style="display:none">
@@ -1017,7 +1113,7 @@ function verifyJwt(token, secret) {
1017
1113
  }
1018
1114
  }
1019
1115
  var DEFAULT_NETWORK2 = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
1020
- var USDC_DECIMALS2 = 6;
1116
+ var USDC_DECIMALS3 = 6;
1021
1117
  function x402AccessPass(config) {
1022
1118
  const {
1023
1119
  payTo,
@@ -1040,7 +1136,7 @@ function x402AccessPass(config) {
1040
1136
  }
1041
1137
  const log = verbose ? console.log.bind(console, "[x402:access-pass]") : () => {
1042
1138
  };
1043
- const decimals = asset?.decimals ?? USDC_DECIMALS2;
1139
+ const decimals = asset?.decimals ?? USDC_DECIMALS3;
1044
1140
  const builtTiers = [];
1045
1141
  if (tierPrices) {
1046
1142
  for (const [id, price] of Object.entries(tierPrices)) {
@@ -1304,7 +1400,6 @@ function formatPricing(config) {
1304
1400
 
1305
1401
  // src/server/token-pricing.ts
1306
1402
  var import_crypto3 = require("crypto");
1307
- var import_tiktoken = require("tiktoken");
1308
1403
 
1309
1404
  // src/server/model-registry.ts
1310
1405
  var STANDARD_PARAMS = {
@@ -1881,17 +1976,30 @@ var MODEL_PRICING_MAP = Object.fromEntries(
1881
1976
  );
1882
1977
 
1883
1978
  // src/server/token-pricing.ts
1979
+ var _tiktoken = null;
1980
+ async function loadTiktoken() {
1981
+ if (_tiktoken) return _tiktoken;
1982
+ try {
1983
+ _tiktoken = await import("tiktoken");
1984
+ return _tiktoken;
1985
+ } catch {
1986
+ throw new Error(
1987
+ 'Token pricing requires the "tiktoken" package. Install with: npm install tiktoken'
1988
+ );
1989
+ }
1990
+ }
1884
1991
  var MODEL_PRICING = MODEL_PRICING_MAP;
1885
1992
  var DEFAULT_MODEL = "gpt-4o-mini";
1886
- function getEncodingForModel(model) {
1993
+ async function getEncodingForModel(model) {
1994
+ const tiktoken = await loadTiktoken();
1887
1995
  try {
1888
- return (0, import_tiktoken.encoding_for_model)(model);
1996
+ return tiktoken.encoding_for_model(model);
1889
1997
  } catch {
1890
- return (0, import_tiktoken.get_encoding)("cl100k_base");
1998
+ return tiktoken.get_encoding("cl100k_base");
1891
1999
  }
1892
2000
  }
1893
- function countTokens(text, model = DEFAULT_MODEL) {
1894
- const encoding = getEncodingForModel(model);
2001
+ async function countTokens(text, model = DEFAULT_MODEL) {
2002
+ const encoding = await getEncodingForModel(model);
1895
2003
  try {
1896
2004
  const tokens = encoding.encode(text);
1897
2005
  return tokens.length;
@@ -1925,17 +2033,17 @@ function createTokenPricing(config = {}) {
1925
2033
  decimals: config.decimals ?? 6
1926
2034
  };
1927
2035
  const { minUsd, maxUsd, decimals } = fullConfig;
1928
- function countTokensInternal(input) {
2036
+ async function countTokensInternal(input) {
1929
2037
  if (customTokenizer) {
1930
2038
  return customTokenizer(input);
1931
2039
  }
1932
2040
  return countTokens(input, model);
1933
2041
  }
1934
- function calculate(input, systemPrompt) {
2042
+ async function calculate(input, systemPrompt) {
1935
2043
  const fullInput = systemPrompt ? `${systemPrompt}
1936
2044
 
1937
2045
  ${input}` : input;
1938
- const inputTokens = countTokensInternal(fullInput);
2046
+ const inputTokens = await countTokensInternal(fullInput);
1939
2047
  let usdAmount = inputTokens / 1e6 * modelInfo.input;
1940
2048
  usdAmount = Math.max(usdAmount, minUsd);
1941
2049
  usdAmount = Math.min(usdAmount, maxUsd);
@@ -1954,9 +2062,9 @@ ${input}` : input;
1954
2062
  quoteHash
1955
2063
  };
1956
2064
  }
1957
- function validateQuote(input, quoteHash) {
2065
+ async function validateQuote(input, quoteHash) {
1958
2066
  if (!quoteHash) return false;
1959
- const inputTokens = countTokensInternal(input);
2067
+ const inputTokens = await countTokensInternal(input);
1960
2068
  const expectedHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);
1961
2069
  return expectedHash === quoteHash;
1962
2070
  }
@@ -1991,97 +2099,6 @@ function formatTokenPricing(model = DEFAULT_MODEL) {
1991
2099
  return `$${pricing.input.toFixed(2)} per 1M tokens (${actualModel})`;
1992
2100
  }
1993
2101
 
1994
- // src/server/stripe-payto.ts
1995
- var STRIPE_NETWORK_KEYS = {
1996
- "base": "base",
1997
- "base-sepolia": "base_sepolia"
1998
- };
1999
- var CAIP2_NETWORKS = {
2000
- "base": "eip155:8453",
2001
- "base-sepolia": "eip155:84532"
2002
- };
2003
- var USDC_DECIMALS3 = 6;
2004
- function stripePayTo(secretKeyOrConfig) {
2005
- const config = typeof secretKeyOrConfig === "string" ? { secretKey: secretKeyOrConfig } : secretKeyOrConfig;
2006
- const networkName = config.network ?? "base";
2007
- const stripeNetworkKey = STRIPE_NETWORK_KEYS[networkName] ?? "base";
2008
- const caip2Network = CAIP2_NETWORKS[networkName] ?? "eip155:8453";
2009
- const apiVersion = config.apiVersion ?? "2026-01-28.clover";
2010
- let stripeClient = null;
2011
- async function getStripe() {
2012
- if (stripeClient) return stripeClient;
2013
- try {
2014
- const { default: Stripe } = await import("stripe");
2015
- stripeClient = new Stripe(config.secretKey, {
2016
- apiVersion,
2017
- appInfo: {
2018
- name: "@dexterai/x402",
2019
- url: "https://dexter.cash/sdk"
2020
- }
2021
- });
2022
- return stripeClient;
2023
- } catch {
2024
- throw new Error(
2025
- 'The "stripe" package is required for stripePayTo(). Install it with: npm install stripe'
2026
- );
2027
- }
2028
- }
2029
- const provider = async (context) => {
2030
- if (context.paymentHeader) {
2031
- try {
2032
- const decoded = JSON.parse(
2033
- Buffer.from(context.paymentHeader, "base64").toString()
2034
- );
2035
- const toAddress = decoded.payload?.authorization?.to;
2036
- if (toAddress && typeof toAddress === "string") {
2037
- return toAddress;
2038
- }
2039
- const acceptedPayTo = decoded.accepted?.payTo;
2040
- if (acceptedPayTo && typeof acceptedPayTo === "string") {
2041
- return acceptedPayTo;
2042
- }
2043
- } catch {
2044
- }
2045
- throw new Error(
2046
- "Could not extract deposit address from payment header. Ensure the client is sending a valid x402 PAYMENT-SIGNATURE."
2047
- );
2048
- }
2049
- const stripe = await getStripe();
2050
- const amountAtomic = context.amountAtomic ? parseInt(context.amountAtomic, 10) : 1e4;
2051
- const amountInCents = Math.max(1, Math.round(amountAtomic / Math.pow(10, USDC_DECIMALS3 - 2)));
2052
- const paymentIntent = await stripe.paymentIntents.create({
2053
- amount: amountInCents,
2054
- currency: "usd",
2055
- payment_method_types: ["crypto"],
2056
- payment_method_data: { type: "crypto" },
2057
- payment_method_options: {
2058
- crypto: { mode: "custom" }
2059
- },
2060
- confirm: true
2061
- });
2062
- const nextAction = paymentIntent.next_action;
2063
- if (!nextAction?.crypto_collect_deposit_details) {
2064
- throw new Error(
2065
- "Stripe PaymentIntent did not return crypto deposit details. Ensure your Stripe account has crypto payins enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto"
2066
- );
2067
- }
2068
- const depositDetails = nextAction.crypto_collect_deposit_details;
2069
- const payToAddress = depositDetails.deposit_addresses?.[stripeNetworkKey]?.address;
2070
- if (!payToAddress) {
2071
- throw new Error(
2072
- `No deposit address found for network "${stripeNetworkKey}". Available networks: ${Object.keys(depositDetails.deposit_addresses || {}).join(", ")}`
2073
- );
2074
- }
2075
- return payToAddress;
2076
- };
2077
- provider._x402Defaults = {
2078
- network: caip2Network,
2079
- facilitatorUrl: "https://x402.dexter.cash"
2080
- };
2081
- provider._stripeNetwork = caip2Network;
2082
- return provider;
2083
- }
2084
-
2085
2102
  // src/server/index.ts
2086
2103
  var import_x402_ads_types = require("@dexterai/x402-ads-types");
2087
2104
  // Annotate the CommonJS export names for ESM import in node:
@@ -2100,6 +2117,7 @@ var import_x402_ads_types = require("@dexterai/x402-ads-types");
2100
2117
  createDynamicPricing,
2101
2118
  createTokenPricing,
2102
2119
  createX402Server,
2120
+ escapeHtml,
2103
2121
  estimateCost,
2104
2122
  findModel,
2105
2123
  formatModelPricing,