@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.
- package/dist/react/index.cjs +4 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +4 -3
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +98 -0
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +68 -1
- package/dist/server/index.d.ts +68 -1
- package/dist/server/index.js +97 -0
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.cts
CHANGED
|
@@ -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 };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/server/index.js
CHANGED
|
@@ -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 →</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
|