@dexterai/x402 1.5.4 → 1.6.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.
@@ -308,6 +308,73 @@ interface X402Request extends Request {
308
308
  */
309
309
  declare function x402Middleware(config: X402MiddlewareConfig): RequestHandler;
310
310
 
311
+ /**
312
+ * x402 Browser Support Middleware
313
+ *
314
+ * Express middleware that automatically renders a branded HTML paywall page
315
+ * when a browser (Accept: text/html) receives a 402 Payment Required response.
316
+ * API clients continue to receive the standard JSON response unchanged.
317
+ *
318
+ * This is a protocol-level concern: the 402 response is part of x402, and
319
+ * providing a human-readable payment page for browsers is the natural UX.
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * import express from 'express';
324
+ * import { x402Middleware, x402BrowserSupport } from '@dexterai/x402/server';
325
+ *
326
+ * const app = express();
327
+ * app.use(express.json());
328
+ * app.use(x402BrowserSupport()); // one line -- all 402s render HTML for browsers
329
+ *
330
+ * app.post('/api/data',
331
+ * x402Middleware({ payTo: '...', amount: '0.01' }),
332
+ * (req, res) => res.json({ data: 'protected' })
333
+ * );
334
+ * ```
335
+ */
336
+
337
+ /**
338
+ * Configuration for x402BrowserSupport middleware.
339
+ * All fields are optional -- sensible defaults are used.
340
+ */
341
+ interface X402BrowserSupportConfig {
342
+ /**
343
+ * Custom title shown on the paywall page.
344
+ * @default 'Payment Required'
345
+ */
346
+ title?: string;
347
+ /**
348
+ * Custom branding text shown at the bottom.
349
+ * @default 'Powered by x402'
350
+ */
351
+ branding?: string;
352
+ /**
353
+ * URL to link for SDK/documentation.
354
+ * @default 'https://x402.org'
355
+ */
356
+ sdkUrl?: string;
357
+ /**
358
+ * Whether to include the request method and path on the page.
359
+ * @default true
360
+ */
361
+ showEndpoint?: boolean;
362
+ }
363
+ /**
364
+ * Create x402 browser support middleware.
365
+ *
366
+ * Wraps `res.json()` to intercept 402 Payment Required responses.
367
+ * When the request is from a browser (Accept: text/html) and no
368
+ * payment-signature header is present, renders a branded HTML paywall
369
+ * instead of raw JSON.
370
+ *
371
+ * API clients are completely unaffected -- they receive normal JSON.
372
+ *
373
+ * @param config - Optional configuration
374
+ * @returns Express middleware
375
+ */
376
+ declare function x402BrowserSupport(config?: X402BrowserSupportConfig): RequestHandler;
377
+
311
378
  /**
312
379
  * x402 Access Pass Middleware
313
380
  *
@@ -849,4 +916,4 @@ declare const MODEL_PRICING_MAP: Record<string, {
849
916
  tier: string;
850
917
  }>;
851
918
 
852
- export { type AssetConfig, type BuildRequirementsOptions, type DynamicPricing, type DynamicPricingConfig, FacilitatorClient, MODEL_PRICING, MODEL_PRICING_MAP, MODEL_REGISTRY, type ModelApiType, type ModelDefinition, type ModelModality, type ModelParameters, type ModelPricing$1 as ModelPricing, type ModelTier, PaymentAccept, PaymentRequired, type PriceQuote, type ModelPricing as RegistryModelPricing, SettleResponse, type SupportedKind, type SupportedResponse, type TokenPriceQuote, type TokenPricing, type TokenPricingConfig, VerifyResponse, type X402AccessPassConfig, type X402AccessPassRequest, type X402MiddlewareConfig, type X402Request, type X402Server, type X402ServerConfig, countTokens, createDynamicPricing, createTokenPricing, createX402Server, estimateCost, findModel, formatModelPricing, formatPricing, formatTokenPricing, getActiveModels, getAvailableModelIds, getAvailableModels, getCheapestModel, getModel, getModelsByFamily, getModelsByTier, getTextModels, isValidModel, isValidModelId, x402AccessPass, x402Middleware };
919
+ export { type AssetConfig, type BuildRequirementsOptions, type DynamicPricing, type DynamicPricingConfig, FacilitatorClient, MODEL_PRICING, MODEL_PRICING_MAP, MODEL_REGISTRY, type ModelApiType, type ModelDefinition, type ModelModality, type ModelParameters, type ModelPricing$1 as ModelPricing, type ModelTier, PaymentAccept, PaymentRequired, type PriceQuote, type ModelPricing as RegistryModelPricing, SettleResponse, type SupportedKind, type SupportedResponse, type TokenPriceQuote, type TokenPricing, type TokenPricingConfig, VerifyResponse, type X402AccessPassConfig, type X402AccessPassRequest, type X402BrowserSupportConfig, type X402MiddlewareConfig, type X402Request, type X402Server, type X402ServerConfig, countTokens, createDynamicPricing, createTokenPricing, createX402Server, estimateCost, findModel, formatModelPricing, formatPricing, formatTokenPricing, getActiveModels, getAvailableModelIds, getAvailableModels, getCheapestModel, getModel, getModelsByFamily, getModelsByTier, getTextModels, isValidModel, isValidModelId, x402AccessPass, x402BrowserSupport, x402Middleware };
@@ -308,6 +308,73 @@ interface X402Request extends Request {
308
308
  */
309
309
  declare function x402Middleware(config: X402MiddlewareConfig): RequestHandler;
310
310
 
311
+ /**
312
+ * x402 Browser Support Middleware
313
+ *
314
+ * Express middleware that automatically renders a branded HTML paywall page
315
+ * when a browser (Accept: text/html) receives a 402 Payment Required response.
316
+ * API clients continue to receive the standard JSON response unchanged.
317
+ *
318
+ * This is a protocol-level concern: the 402 response is part of x402, and
319
+ * providing a human-readable payment page for browsers is the natural UX.
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * import express from 'express';
324
+ * import { x402Middleware, x402BrowserSupport } from '@dexterai/x402/server';
325
+ *
326
+ * const app = express();
327
+ * app.use(express.json());
328
+ * app.use(x402BrowserSupport()); // one line -- all 402s render HTML for browsers
329
+ *
330
+ * app.post('/api/data',
331
+ * x402Middleware({ payTo: '...', amount: '0.01' }),
332
+ * (req, res) => res.json({ data: 'protected' })
333
+ * );
334
+ * ```
335
+ */
336
+
337
+ /**
338
+ * Configuration for x402BrowserSupport middleware.
339
+ * All fields are optional -- sensible defaults are used.
340
+ */
341
+ interface X402BrowserSupportConfig {
342
+ /**
343
+ * Custom title shown on the paywall page.
344
+ * @default 'Payment Required'
345
+ */
346
+ title?: string;
347
+ /**
348
+ * Custom branding text shown at the bottom.
349
+ * @default 'Powered by x402'
350
+ */
351
+ branding?: string;
352
+ /**
353
+ * URL to link for SDK/documentation.
354
+ * @default 'https://x402.org'
355
+ */
356
+ sdkUrl?: string;
357
+ /**
358
+ * Whether to include the request method and path on the page.
359
+ * @default true
360
+ */
361
+ showEndpoint?: boolean;
362
+ }
363
+ /**
364
+ * Create x402 browser support middleware.
365
+ *
366
+ * Wraps `res.json()` to intercept 402 Payment Required responses.
367
+ * When the request is from a browser (Accept: text/html) and no
368
+ * payment-signature header is present, renders a branded HTML paywall
369
+ * instead of raw JSON.
370
+ *
371
+ * API clients are completely unaffected -- they receive normal JSON.
372
+ *
373
+ * @param config - Optional configuration
374
+ * @returns Express middleware
375
+ */
376
+ declare function x402BrowserSupport(config?: X402BrowserSupportConfig): RequestHandler;
377
+
311
378
  /**
312
379
  * x402 Access Pass Middleware
313
380
  *
@@ -849,4 +916,4 @@ declare const MODEL_PRICING_MAP: Record<string, {
849
916
  tier: string;
850
917
  }>;
851
918
 
852
- export { type AssetConfig, type BuildRequirementsOptions, type DynamicPricing, type DynamicPricingConfig, FacilitatorClient, MODEL_PRICING, MODEL_PRICING_MAP, MODEL_REGISTRY, type ModelApiType, type ModelDefinition, type ModelModality, type ModelParameters, type ModelPricing$1 as ModelPricing, type ModelTier, PaymentAccept, PaymentRequired, type PriceQuote, type ModelPricing as RegistryModelPricing, SettleResponse, type SupportedKind, type SupportedResponse, type TokenPriceQuote, type TokenPricing, type TokenPricingConfig, VerifyResponse, type X402AccessPassConfig, type X402AccessPassRequest, type X402MiddlewareConfig, type X402Request, type X402Server, type X402ServerConfig, countTokens, createDynamicPricing, createTokenPricing, createX402Server, estimateCost, findModel, formatModelPricing, formatPricing, formatTokenPricing, getActiveModels, getAvailableModelIds, getAvailableModels, getCheapestModel, getModel, getModelsByFamily, getModelsByTier, getTextModels, isValidModel, isValidModelId, x402AccessPass, x402Middleware };
919
+ export { type AssetConfig, type BuildRequirementsOptions, type DynamicPricing, type DynamicPricingConfig, FacilitatorClient, MODEL_PRICING, MODEL_PRICING_MAP, MODEL_REGISTRY, type ModelApiType, type ModelDefinition, type ModelModality, type ModelParameters, type ModelPricing$1 as ModelPricing, type ModelTier, PaymentAccept, PaymentRequired, type PriceQuote, type ModelPricing as RegistryModelPricing, SettleResponse, type SupportedKind, type SupportedResponse, type TokenPriceQuote, type TokenPricing, type TokenPricingConfig, VerifyResponse, type X402AccessPassConfig, type X402AccessPassRequest, type X402BrowserSupportConfig, type X402MiddlewareConfig, type X402Request, type X402Server, type X402ServerConfig, countTokens, createDynamicPricing, createTokenPricing, createX402Server, estimateCost, findModel, formatModelPricing, formatPricing, formatTokenPricing, getActiveModels, getAvailableModelIds, getAvailableModels, getCheapestModel, getModel, getModelsByFamily, getModelsByTier, getTextModels, isValidModel, isValidModelId, x402AccessPass, x402BrowserSupport, x402Middleware };
@@ -357,6 +357,102 @@ function x402Middleware(config) {
357
357
  };
358
358
  }
359
359
 
360
+ // src/server/browser-support.ts
361
+ function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config) {
362
+ let price = "?";
363
+ let description = "This resource requires payment";
364
+ let network = "";
365
+ try {
366
+ const decoded = JSON.parse(Buffer.from(paymentRequiredHeader, "base64").toString());
367
+ const accept = decoded.accepts?.[0];
368
+ if (accept) {
369
+ const amount = accept.amount || accept.maxAmountRequired || "0";
370
+ const decimals = accept.extra?.decimals || 6;
371
+ price = (Number(amount) / Math.pow(10, decimals)).toFixed(decimals > 4 ? 4 : 2);
372
+ network = accept.network || "";
373
+ }
374
+ if (decoded.resource?.description) {
375
+ description = decoded.resource.description;
376
+ }
377
+ } catch {
378
+ }
379
+ const chainName = network.includes("solana") ? "Solana" : network.includes("eip155") ? "Base" : "Unknown";
380
+ const endpointSection = config.showEndpoint ? `<div class="endpoint"><code>${method} ${requestUrl}</code></div>` : "";
381
+ return `<!DOCTYPE html>
382
+ <html lang="en">
383
+ <head>
384
+ <meta charset="utf-8">
385
+ <meta name="viewport" content="width=device-width,initial-scale=1">
386
+ <title>${config.title} \u2014 $${price} USDC</title>
387
+ <style>
388
+ *{margin:0;padding:0;box-sizing:border-box}
389
+ body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0a0a0a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
390
+ .paywall{max-width:460px;width:100%;background:#141414;border:1px solid #2a2a2a;border-radius:16px;padding:2.5rem;text-align:center}
391
+ .paywall h1{font-size:1.35rem;margin-bottom:.35rem;color:#f1f5f9;font-weight:600}
392
+ .desc{color:#94a3b8;font-size:.95rem;margin-bottom:1.5rem;line-height:1.5}
393
+ .price-badge{display:inline-block;background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;padding:.6rem 2rem;border-radius:999px;font-size:1.75rem;font-weight:700;margin:1rem 0;letter-spacing:-.02em}
394
+ .chain{color:#64748b;font-size:.8rem;margin-bottom:1.5rem}
395
+ .endpoint{background:#1e1e1e;border:1px solid #333;border-radius:8px;padding:.6rem 1rem;margin-bottom:1.5rem}
396
+ .endpoint code{font-family:'SF Mono',Monaco,Consolas,monospace;font-size:.85rem;color:#60a5fa}
397
+ .info{background:#1a1a2e;border:1px solid #2d2d5e;border-radius:10px;padding:1rem 1.25rem;font-size:.85rem;color:#a0aec0;line-height:1.6;text-align:left}
398
+ .info strong{color:#e2e8f0}
399
+ .info code{background:#2a2a3e;padding:2px 6px;border-radius:4px;font-size:.8rem;color:#818cf8;font-family:'SF Mono',Monaco,Consolas,monospace}
400
+ .info a{color:#60a5fa;text-decoration:none;font-weight:600}
401
+ .info a:hover{text-decoration:underline}
402
+ .powered{margin-top:1.5rem;font-size:.75rem;color:#475569}
403
+ .powered a{color:#64748b;text-decoration:none}
404
+ .powered a:hover{color:#94a3b8}
405
+ .x402-badge{display:inline-flex;align-items:center;gap:.35rem;background:#1a1a2e;border:1px solid #2d2d5e;padding:.25rem .75rem;border-radius:999px;font-size:.7rem;color:#818cf8;margin-top:.75rem;font-weight:500}
406
+ </style>
407
+ </head>
408
+ <body>
409
+ <div class="paywall">
410
+ <h1>${config.title}</h1>
411
+ <p class="desc">${description}</p>
412
+ <div class="price-badge">$${price} USDC</div>
413
+ <div class="chain">${chainName} network</div>
414
+ ${endpointSection}
415
+ <div class="info">
416
+ <strong>How to access this endpoint:</strong><br><br>
417
+ Use any x402-compatible client or SDK. The client handles wallet connection and payment automatically.<br><br>
418
+ <code>npm install @dexterai/x402</code><br><br>
419
+ <a href="${config.sdkUrl}">Learn more about x402 &rarr;</a>
420
+ </div>
421
+ <div class="powered">${config.branding}</div>
422
+ <div class="x402-badge">x402 protocol</div>
423
+ </div>
424
+ </body>
425
+ </html>`;
426
+ }
427
+ function x402BrowserSupport(config = {}) {
428
+ const resolvedConfig = {
429
+ title: config.title ?? "Payment Required",
430
+ branding: config.branding ?? 'Powered by <a href="https://x402.org">x402</a>',
431
+ sdkUrl: config.sdkUrl ?? "https://x402.org",
432
+ showEndpoint: config.showEndpoint ?? true
433
+ };
434
+ return (req, res, next) => {
435
+ const originalJson = res.json.bind(res);
436
+ res.json = function(body) {
437
+ if (res.statusCode === 402 && req.accepts("html") && !req.headers["payment-signature"]) {
438
+ const paymentRequired = res.getHeader("PAYMENT-REQUIRED") || res.getHeader("payment-required");
439
+ if (paymentRequired && typeof paymentRequired === "string") {
440
+ const html = generatePaywallHtml(
441
+ paymentRequired,
442
+ req.originalUrl,
443
+ req.method,
444
+ resolvedConfig
445
+ );
446
+ res.status(402).type("html").send(html);
447
+ return res;
448
+ }
449
+ }
450
+ return originalJson(body);
451
+ };
452
+ next();
453
+ };
454
+ }
455
+
360
456
  // src/server/access-pass.ts
361
457
  import crypto from "crypto";
362
458
  var DURATION_REGEX = /^(\d+)(m|h|d|w)$/;
@@ -1396,6 +1492,7 @@ export {
1396
1492
  isValidModel,
1397
1493
  isValidModelId,
1398
1494
  x402AccessPass,
1495
+ x402BrowserSupport,
1399
1496
  x402Middleware
1400
1497
  };
1401
1498
  //# sourceMappingURL=index.js.map