@dexterai/x402 1.5.5 → 1.6.1
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/server/index.cjs +338 -0
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +55 -1
- package/dist/server/index.d.ts +55 -1
- package/dist/server/index.js +337 -0
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.cts
CHANGED
|
@@ -308,6 +308,60 @@ 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
|
+
* Includes a functional "Pay" button using the Solana Wallet Standard --
|
|
319
|
+
* detects Phantom/Solflare/Backpack, constructs a USDC transfer, signs,
|
|
320
|
+
* and submits the payment automatically.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* import express from 'express';
|
|
325
|
+
* import { x402Middleware, x402BrowserSupport } from '@dexterai/x402/server';
|
|
326
|
+
*
|
|
327
|
+
* const app = express();
|
|
328
|
+
* app.use(express.json());
|
|
329
|
+
* app.use(x402BrowserSupport());
|
|
330
|
+
*
|
|
331
|
+
* app.post('/api/data',
|
|
332
|
+
* x402Middleware({ payTo: '...', amount: '0.01' }),
|
|
333
|
+
* (req, res) => res.json({ data: 'protected' })
|
|
334
|
+
* );
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Configuration for x402BrowserSupport middleware.
|
|
340
|
+
*/
|
|
341
|
+
interface X402BrowserSupportConfig {
|
|
342
|
+
/** Custom title shown on the paywall page. @default 'Payment Required' */
|
|
343
|
+
title?: string;
|
|
344
|
+
/** Custom branding text. @default 'Powered by x402' */
|
|
345
|
+
branding?: string;
|
|
346
|
+
/** URL to link for SDK/documentation. @default 'https://x402.org' */
|
|
347
|
+
sdkUrl?: string;
|
|
348
|
+
/** Whether to include the request method and path. @default true */
|
|
349
|
+
showEndpoint?: boolean;
|
|
350
|
+
/** Solana RPC URL for wallet transactions. @default 'https://api.dexter.cash/api/solana/rpc' */
|
|
351
|
+
rpcUrl?: string;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Create x402 browser support middleware.
|
|
355
|
+
*
|
|
356
|
+
* Wraps `res.json()` to intercept 402 Payment Required responses.
|
|
357
|
+
* When the request is from a browser (Accept: text/html) and no
|
|
358
|
+
* payment-signature header is present, renders a branded HTML paywall
|
|
359
|
+
* instead of raw JSON.
|
|
360
|
+
*
|
|
361
|
+
* API clients are completely unaffected -- they receive normal JSON.
|
|
362
|
+
*/
|
|
363
|
+
declare function x402BrowserSupport(config?: X402BrowserSupportConfig): RequestHandler;
|
|
364
|
+
|
|
311
365
|
/**
|
|
312
366
|
* x402 Access Pass Middleware
|
|
313
367
|
*
|
|
@@ -849,4 +903,4 @@ declare const MODEL_PRICING_MAP: Record<string, {
|
|
|
849
903
|
tier: string;
|
|
850
904
|
}>;
|
|
851
905
|
|
|
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 };
|
|
906
|
+
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,60 @@ 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
|
+
* Includes a functional "Pay" button using the Solana Wallet Standard --
|
|
319
|
+
* detects Phantom/Solflare/Backpack, constructs a USDC transfer, signs,
|
|
320
|
+
* and submits the payment automatically.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* import express from 'express';
|
|
325
|
+
* import { x402Middleware, x402BrowserSupport } from '@dexterai/x402/server';
|
|
326
|
+
*
|
|
327
|
+
* const app = express();
|
|
328
|
+
* app.use(express.json());
|
|
329
|
+
* app.use(x402BrowserSupport());
|
|
330
|
+
*
|
|
331
|
+
* app.post('/api/data',
|
|
332
|
+
* x402Middleware({ payTo: '...', amount: '0.01' }),
|
|
333
|
+
* (req, res) => res.json({ data: 'protected' })
|
|
334
|
+
* );
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Configuration for x402BrowserSupport middleware.
|
|
340
|
+
*/
|
|
341
|
+
interface X402BrowserSupportConfig {
|
|
342
|
+
/** Custom title shown on the paywall page. @default 'Payment Required' */
|
|
343
|
+
title?: string;
|
|
344
|
+
/** Custom branding text. @default 'Powered by x402' */
|
|
345
|
+
branding?: string;
|
|
346
|
+
/** URL to link for SDK/documentation. @default 'https://x402.org' */
|
|
347
|
+
sdkUrl?: string;
|
|
348
|
+
/** Whether to include the request method and path. @default true */
|
|
349
|
+
showEndpoint?: boolean;
|
|
350
|
+
/** Solana RPC URL for wallet transactions. @default 'https://api.dexter.cash/api/solana/rpc' */
|
|
351
|
+
rpcUrl?: string;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Create x402 browser support middleware.
|
|
355
|
+
*
|
|
356
|
+
* Wraps `res.json()` to intercept 402 Payment Required responses.
|
|
357
|
+
* When the request is from a browser (Accept: text/html) and no
|
|
358
|
+
* payment-signature header is present, renders a branded HTML paywall
|
|
359
|
+
* instead of raw JSON.
|
|
360
|
+
*
|
|
361
|
+
* API clients are completely unaffected -- they receive normal JSON.
|
|
362
|
+
*/
|
|
363
|
+
declare function x402BrowserSupport(config?: X402BrowserSupportConfig): RequestHandler;
|
|
364
|
+
|
|
311
365
|
/**
|
|
312
366
|
* x402 Access Pass Middleware
|
|
313
367
|
*
|
|
@@ -849,4 +903,4 @@ declare const MODEL_PRICING_MAP: Record<string, {
|
|
|
849
903
|
tier: string;
|
|
850
904
|
}>;
|
|
851
905
|
|
|
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 };
|
|
906
|
+
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,342 @@ function x402Middleware(config) {
|
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
// src/server/browser-support.ts
|
|
361
|
+
var DEXTER_CREST_SVG = `<svg width="36" height="36" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F2681A" d="m324.93,313.11c-115.5,0-231,0-350,0l350,0z"/><path fill="#FDFAF5" d="m230.43,50.62c1.1.85 2.19 1.7 3.32 2.57 6.02 4.8 11.77 9.88 17.46 15.07.92.84.92.84 1.86 1.69 1.82 1.69 3.59 3.42 5.35 5.16.61.56 1.22 1.13 1.84 1.71 5.66 5.76 6.18 10.43 6.13 18.3.02 1.16.04 2.32.06 3.52.06 3.83.06 7.65.07 11.48.02 2.68.05 5.35.08 8.03.05 5.6.09 11.21.1 16.81.02 7.15.09 14.31.17 21.46.06 5.53.1 11.05.13 16.58.02 2.64.04 5.27.07 7.91.18 17.58.12 32.82-11.24 47.32-7.35 7.27-16.54 12.06-25.42 17.22-1.97 1.16-3.94 2.33-5.91 3.49-7.16 4.24-14.34 8.44-21.53 12.62-4.8 2.79-9.59 5.6-14.38 8.42-1.25.73-2.5 1.47-3.79 2.23-2.32 1.36-4.64 2.73-6.96 4.1-27.47 16.09-27.47 16.09-42.16 12.93-8.06-2.28-14.94-5.82-22.16-10.02-1.17-.67-2.34-1.34-3.54-2.04-24.55-14.25-43.58-27.03-51.9-55.58-1.07-4.58-1.54-8.92-1.52-13.61.28-9.5.28-9.5-3.3-17.97-1.81-1.49-3.68-2.92-5.59-4.28-9.19-7.06-12.7-20.03-14.18-31.06-.54-5.77-.55-11.56-.6-17.35-.03-1.32-.07-2.63-.1-3.99-.01-1.26-.02-2.53-.03-3.83-.02-1.15-.03-2.29-.05-3.47.72-4.02 1.94-5.36 5.21-7.74 2.89-.53 2.89-.53 6.07-.46 1.71.02 1.71.02 3.46.05 1.19.04 2.37.08 3.59.12 1.2.02 2.41.04 3.65.06 2.97.05 5.93.13 8.9.23.14-1.35.29-2.7.43-4.08.63-5 1.78-9.74 3.14-14.58.22-.79.43-1.59.66-2.4.53-1.92 1.06-3.84 1.6-5.76-1.55-.45-1.55-.45-3.13-.9-9.52-3.52-17.1-10.95-21.37-20.1-3.81-9.26-3.87-20.34-.29-29.68 6.49-13.99 16.36-23.23 30.66-29.01 49.81-17.69 115.79 8.35 155.13 38.85z"/><path fill="#F2671A" d="m142.93,22.62c.86.19 1.73.39 2.62.59 36.12 8.21 68.79 24.98 95.38 50.75 1.02.98 2.03 1.97 3.08 2.98 10.84 10.66 10.84 10.66 11.05 14.62-2.06 3.55-5.44 4.18-9.17 5.3-.79.25-1.59.49-2.41.75-28.13 8.43-60.95 6.37-87.13-7.16-.86-.49-1.71-.97-2.6-1.48-7.37-4.05-12.59-3.36-20.59-1.54-22.76 4-48.47 1.53-68.69-9.74-4.88-3.88-8.23-8.29-10.21-14.22-.93-10.38-.67-18.44 5.83-26.83 19.57-23.38 55.99-20.36 82.83-14z"/><path fill="#F16619" d="m44.93,129.12c27.36-.03 54.72-.05 82.08-.06 12.7-.01 25.41-.01 38.11-.03 11.07-.01 22.14-.02 33.2-.02 5.86 0 11.73-.01 17.59-.01 5.51-.01 11.03-.01 16.54-.01 2.03 0 4.06 0 6.09-.01 2.76-.01 5.52 0 8.28 0 .81 0 1.63-.01 2.47-.01 5.51.02 5.51.02 6.81 1.32.22 3.43.22 3.43 0 7-2.75 2.75-3.42 2.66-7.15 2.82-1.41.07-1.41.07-2.85.14-1.47.05-1.47.05-2.98.11-1.49.07-1.49.07-3 .14-2.45.11-4.9.21-7.35.3-.2 1.3-.4 2.59-.6 3.93-2.57 16.08-5.93 29.89-18.89 40.86-10.35 7.28-21.87 8.49-34.17 7.71-13.11-2.33-22.52-9.19-30.33-19.83-4.49-7.64-4.8-17.05-5.83-25.67-4.24.39-8.47.77-12.83 1.17-.28 1.84-.28 1.84-.56 3.71-2.32 14.39-5.63 23.35-16.95 33.11-2.32 1.67-2.32 1.67-4.65 1.67 4 4.67 9.06 6.59 14.87 8.24 3.79 1.09 3.79 1.09 6.12 3.43-.65 5.31-.65 5.31-2.33 7-8.42-.27-15.13-2.29-22.17-7-1.09-1.21-2.17-2.43-3.25-3.65-2.72-2.81-4.45-3.84-8.36-4.16-1.67-.02-3.34-.02-5.01.01-1.77-.04-3.54-.09-5.3-.15-1.27-.04-1.27-.04-2.56-.08-9.26-.54-17.6-4.56-24.51-10.64-9.58-11.11-11.03-22.56-10.72-36.82.02-1.4.03-2.8.05-4.24.04-3.42.1-6.85.17-10.27z"/><path fill="#F26117" d="m172.68,203.08c7.27.09 13.23 1.97 18.87 6.65 2.88 3.07 3.86 5.12 4.25 9.32-.12 1.01-.24 2.02-.36 3.06-2.55.95-2.55.95-5.83 1.17-3.28-2.84-3.28-2.84-5.83-5.83-.36.58-.71 1.16-1.08 1.75-7.6 11.29-20.06 17.74-33.05 21.09-20.36 3.1-36.81-1.66-53.37-13.73-2.33-2.11-2.33-2.11-4.67-5.61.42-3.45.99-4.49 3.5-7 4.07.37 5.95 2.13 8.75 4.96 9.81 8.93 22.53 11.87 35.51 11.69 11.74-1.05 22.38-5.85 31.57-13.15 2.06-2.45 2.06-2.45 3.5-4.67-1.66.07-1.66.07-3.35.15-3.65-.15-3.65-.15-5.98-2.48.75-6.18 1.46-7.19 7.58-7.36z"/></g></svg>`;
|
|
362
|
+
var DEXTER_STYLES = `
|
|
363
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap');
|
|
364
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
365
|
+
body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
|
|
366
|
+
.card{max-width:460px;width:100%;background:rgba(20,20,20,.85);border:1px solid rgba(242,107,26,.12);border-radius:8px;padding:2rem 2rem 1.75rem;text-align:center;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
|
|
367
|
+
.crest{margin:0 auto .75rem}
|
|
368
|
+
h1{font-family:'Orbitron',sans-serif;font-size:1.15rem;font-weight:700;color:#f1f5f9;letter-spacing:.04em;margin-bottom:.35rem}
|
|
369
|
+
.desc{color:#94a3b8;font-size:.9rem;margin-bottom:1.25rem;line-height:1.5}
|
|
370
|
+
.price{font-family:'Orbitron',sans-serif;font-size:1.6rem;font-weight:700;color:#F26B1A;margin:.75rem 0 .25rem}
|
|
371
|
+
.chain{color:#525252;font-size:.75rem;margin-bottom:1.25rem;letter-spacing:.03em}
|
|
372
|
+
.endpoint{background:rgba(242,107,26,.06);border:1px solid rgba(242,107,26,.12);border-radius:6px;padding:.5rem .75rem;margin-bottom:1.25rem}
|
|
373
|
+
.endpoint code{font-family:'SF Mono',Monaco,Consolas,monospace;font-size:.8rem;color:#F26B1A}
|
|
374
|
+
.info{background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);border-radius:6px;padding:.85rem 1rem;font-size:.82rem;color:#737373;line-height:1.6;text-align:left}
|
|
375
|
+
.info strong{color:#a3a3a3}
|
|
376
|
+
.info code{background:rgba(242,107,26,.08);padding:2px 5px;border-radius:3px;font-size:.78rem;color:#F26B1A;font-family:'SF Mono',Monaco,Consolas,monospace}
|
|
377
|
+
.info a{color:#F26B1A;text-decoration:none;font-weight:600}
|
|
378
|
+
.info a:hover{text-decoration:underline}
|
|
379
|
+
.footer{margin-top:1.25rem;display:flex;align-items:center;justify-content:center;gap:.75rem;font-size:.7rem;color:#404040}
|
|
380
|
+
.footer a{color:#525252;text-decoration:none}
|
|
381
|
+
.footer a:hover{color:#737373}
|
|
382
|
+
.sep{width:3px;height:3px;border-radius:50%;background:#333}
|
|
383
|
+
`;
|
|
384
|
+
var PAY_BUTTON_STYLES = `
|
|
385
|
+
.pay-section{margin:1.25rem 0}
|
|
386
|
+
.pay-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;background:linear-gradient(135deg,#F26B1A,#D13F00);color:#fff;border:none;padding:.65rem 2rem;border-radius:6px;font-family:'Inter',sans-serif;font-size:.95rem;font-weight:600;cursor:pointer;transition:opacity .15s,transform .1s;min-width:180px}
|
|
387
|
+
.pay-btn:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}
|
|
388
|
+
.pay-btn:active:not(:disabled){transform:translateY(0)}
|
|
389
|
+
.pay-btn:disabled{opacity:.6;cursor:not-allowed}
|
|
390
|
+
.pay-btn .spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite}
|
|
391
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
392
|
+
.pay-status{font-size:.8rem;color:#737373;margin-top:.5rem;min-height:1.2em}
|
|
393
|
+
.pay-status.error{color:#ef4444}
|
|
394
|
+
.pay-status.success{color:#22c55e}
|
|
395
|
+
.pay-alt{font-size:.78rem;color:#404040;margin-top:.75rem}
|
|
396
|
+
.pay-alt a{color:#F26B1A;text-decoration:none}
|
|
397
|
+
.result-box{background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.15);border-radius:6px;padding:.75rem;margin-top:.75rem;text-align:left;font-size:.78rem;max-height:200px;overflow:auto}
|
|
398
|
+
.result-box pre{color:#94a3b8;font-family:'SF Mono',Monaco,Consolas,monospace;white-space:pre-wrap;word-break:break-all}
|
|
399
|
+
.no-wallet{font-size:.82rem;color:#737373;margin:1rem 0}
|
|
400
|
+
`;
|
|
401
|
+
var PAY_SCRIPT = `
|
|
402
|
+
<script type="module">
|
|
403
|
+
// Payment data is embedded in #x402-data attributes
|
|
404
|
+
const dataEl = document.getElementById('x402-data');
|
|
405
|
+
if (!dataEl) throw new Error('Missing payment data');
|
|
406
|
+
|
|
407
|
+
const requirements = JSON.parse(atob(dataEl.dataset.requirements));
|
|
408
|
+
const requestMethod = dataEl.dataset.method;
|
|
409
|
+
const requestUrl = dataEl.dataset.url;
|
|
410
|
+
const rpcUrl = dataEl.dataset.rpc || 'https://api.dexter.cash/api/solana/rpc';
|
|
411
|
+
|
|
412
|
+
// Detect wallet provider
|
|
413
|
+
function getWalletProvider() {
|
|
414
|
+
if (window.phantom?.solana?.isPhantom) return { name: 'Phantom', provider: window.phantom.solana };
|
|
415
|
+
if (window.solflare?.isSolflare) return { name: 'Solflare', provider: window.solflare };
|
|
416
|
+
if (window.backpack) return { name: 'Backpack', provider: window.backpack };
|
|
417
|
+
// Generic wallet-standard fallback
|
|
418
|
+
if (window.solana) return { name: 'Wallet', provider: window.solana };
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const walletInfo = getWalletProvider();
|
|
423
|
+
const btn = document.getElementById('pay-btn');
|
|
424
|
+
const status = document.getElementById('pay-status');
|
|
425
|
+
const section = document.getElementById('pay-section');
|
|
426
|
+
const noWallet = document.getElementById('no-wallet');
|
|
427
|
+
|
|
428
|
+
if (walletInfo && btn) {
|
|
429
|
+
section.style.display = 'block';
|
|
430
|
+
if (noWallet) noWallet.style.display = 'none';
|
|
431
|
+
} else if (noWallet) {
|
|
432
|
+
noWallet.style.display = 'block';
|
|
433
|
+
if (section) section.style.display = 'none';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Preload Solana libraries in background
|
|
437
|
+
let solanaLibs = null;
|
|
438
|
+
const preload = (async () => {
|
|
439
|
+
try {
|
|
440
|
+
const [web3, spl] = await Promise.all([
|
|
441
|
+
import('https://esm.sh/@solana/web3.js@1.98.0'),
|
|
442
|
+
import('https://esm.sh/@solana/spl-token@0.4.9'),
|
|
443
|
+
]);
|
|
444
|
+
solanaLibs = { web3, spl };
|
|
445
|
+
} catch (e) {
|
|
446
|
+
console.warn('[x402] Failed to preload Solana libraries:', e);
|
|
447
|
+
}
|
|
448
|
+
})();
|
|
449
|
+
|
|
450
|
+
function setStatus(msg, type) {
|
|
451
|
+
if (!status) return;
|
|
452
|
+
status.textContent = msg;
|
|
453
|
+
status.className = 'pay-status' + (type ? ' ' + type : '');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function setBtnState(text, disabled, loading) {
|
|
457
|
+
if (!btn) return;
|
|
458
|
+
btn.disabled = disabled;
|
|
459
|
+
btn.innerHTML = loading
|
|
460
|
+
? '<span class="spinner"></span>' + text
|
|
461
|
+
: text;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (btn) {
|
|
465
|
+
btn.addEventListener('click', async () => {
|
|
466
|
+
if (!walletInfo) return;
|
|
467
|
+
const { provider } = walletInfo;
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
// 1. Connect wallet
|
|
471
|
+
setBtnState('Connecting...', true, true);
|
|
472
|
+
setStatus('');
|
|
473
|
+
await provider.connect();
|
|
474
|
+
|
|
475
|
+
if (!provider.publicKey) {
|
|
476
|
+
throw new Error('Wallet did not provide a public key');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 2. Load Solana libraries (should already be cached from preload)
|
|
480
|
+
setBtnState('Preparing...', true, true);
|
|
481
|
+
await preload;
|
|
482
|
+
if (!solanaLibs) {
|
|
483
|
+
// Retry once
|
|
484
|
+
const [web3, spl] = await Promise.all([
|
|
485
|
+
import('https://esm.sh/@solana/web3.js@1.98.0'),
|
|
486
|
+
import('https://esm.sh/@solana/spl-token@0.4.9'),
|
|
487
|
+
]);
|
|
488
|
+
solanaLibs = { web3, spl };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const { web3, spl } = solanaLibs;
|
|
492
|
+
const { PublicKey, Connection, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = web3;
|
|
493
|
+
const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } = spl;
|
|
494
|
+
|
|
495
|
+
// 3. Parse payment requirements
|
|
496
|
+
const accept = requirements.accepts[0];
|
|
497
|
+
if (!accept) throw new Error('No payment method available');
|
|
498
|
+
|
|
499
|
+
const payTo = new PublicKey(accept.payTo);
|
|
500
|
+
const amount = BigInt(accept.amount || accept.maxAmountRequired);
|
|
501
|
+
const mintPubkey = new PublicKey(accept.asset);
|
|
502
|
+
const feePayer = accept.extra?.feePayer ? new PublicKey(accept.extra.feePayer) : provider.publicKey;
|
|
503
|
+
const userPubkey = provider.publicKey;
|
|
504
|
+
|
|
505
|
+
// 4. Build transaction
|
|
506
|
+
setBtnState('Building tx...', true, true);
|
|
507
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
508
|
+
|
|
509
|
+
const instructions = [];
|
|
510
|
+
|
|
511
|
+
// ComputeBudget
|
|
512
|
+
instructions.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 12000 }));
|
|
513
|
+
instructions.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 }));
|
|
514
|
+
|
|
515
|
+
// Determine token program
|
|
516
|
+
const mintInfo = await connection.getAccountInfo(mintPubkey, 'confirmed');
|
|
517
|
+
if (!mintInfo) throw new Error('Token mint not found');
|
|
518
|
+
const programId = mintInfo.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
519
|
+
|
|
520
|
+
const mint = await getMint(connection, mintPubkey, undefined, programId);
|
|
521
|
+
|
|
522
|
+
// ATAs
|
|
523
|
+
const sourceAta = await getAssociatedTokenAddress(mintPubkey, userPubkey, false, programId);
|
|
524
|
+
const destAta = await getAssociatedTokenAddress(mintPubkey, payTo, false, programId);
|
|
525
|
+
|
|
526
|
+
// Verify source exists
|
|
527
|
+
const sourceInfo = await connection.getAccountInfo(sourceAta, 'confirmed');
|
|
528
|
+
if (!sourceInfo) throw new Error('No USDC token account found. Make sure you have USDC in your wallet.');
|
|
529
|
+
|
|
530
|
+
// TransferChecked
|
|
531
|
+
instructions.push(createTransferCheckedInstruction(sourceAta, mintPubkey, destAta, userPubkey, amount, mint.decimals, [], programId));
|
|
532
|
+
|
|
533
|
+
const { blockhash } = await connection.getLatestBlockhash('confirmed');
|
|
534
|
+
const message = new TransactionMessage({ payerKey: feePayer, recentBlockhash: blockhash, instructions }).compileToV0Message();
|
|
535
|
+
const transaction = new VersionedTransaction(message);
|
|
536
|
+
|
|
537
|
+
// 5. Sign
|
|
538
|
+
setBtnState('Sign in wallet...', true, true);
|
|
539
|
+
setStatus('Approve the transaction in your wallet');
|
|
540
|
+
const signed = await provider.signTransaction(transaction);
|
|
541
|
+
const serialized = signed.serialize();
|
|
542
|
+
|
|
543
|
+
// Convert Uint8Array to base64
|
|
544
|
+
let payload = '';
|
|
545
|
+
const bytes = new Uint8Array(serialized);
|
|
546
|
+
const chunk = 8192;
|
|
547
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
548
|
+
payload += String.fromCharCode.apply(null, bytes.slice(i, i + chunk));
|
|
549
|
+
}
|
|
550
|
+
payload = btoa(payload);
|
|
551
|
+
|
|
552
|
+
// 6. Build payment-signature header (x402 v2 format)
|
|
553
|
+
const paymentSignature = {
|
|
554
|
+
x402Version: accept.x402Version ?? 2,
|
|
555
|
+
resource: requirements.resource,
|
|
556
|
+
accepted: accept,
|
|
557
|
+
payload,
|
|
558
|
+
};
|
|
559
|
+
const paymentHeader = btoa(JSON.stringify(paymentSignature));
|
|
560
|
+
|
|
561
|
+
// 7. Submit payment
|
|
562
|
+
setBtnState('Verifying...', true, true);
|
|
563
|
+
setStatus('Payment submitted, verifying...');
|
|
564
|
+
|
|
565
|
+
const response = await fetch(requestUrl, {
|
|
566
|
+
method: requestMethod,
|
|
567
|
+
headers: {
|
|
568
|
+
'Content-Type': 'application/json',
|
|
569
|
+
'PAYMENT-SIGNATURE': paymentHeader,
|
|
570
|
+
},
|
|
571
|
+
body: requestMethod !== 'GET' ? '{}' : undefined,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
if (response.ok) {
|
|
575
|
+
const data = await response.json();
|
|
576
|
+
setBtnState('Paid', true, false);
|
|
577
|
+
setStatus('Payment successful', 'success');
|
|
578
|
+
// Show response
|
|
579
|
+
const resultBox = document.createElement('div');
|
|
580
|
+
resultBox.className = 'result-box';
|
|
581
|
+
resultBox.innerHTML = '<pre>' + JSON.stringify(data, null, 2).replace(/</g, '<') + '</pre>';
|
|
582
|
+
section.appendChild(resultBox);
|
|
583
|
+
} else {
|
|
584
|
+
const err = await response.json().catch(() => ({ error: 'Payment verification failed' }));
|
|
585
|
+
throw new Error(err.error || err.reason || 'Payment failed');
|
|
586
|
+
}
|
|
587
|
+
} catch (err) {
|
|
588
|
+
console.error('[x402] Payment error:', err);
|
|
589
|
+
setBtnState('Pay $' + document.getElementById('price-value').textContent, false, false);
|
|
590
|
+
setStatus(err.message || 'Payment failed', 'error');
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
</script>
|
|
595
|
+
`;
|
|
596
|
+
function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config, rpcUrl) {
|
|
597
|
+
let price = "?";
|
|
598
|
+
let description = "This resource requires payment";
|
|
599
|
+
let network = "";
|
|
600
|
+
try {
|
|
601
|
+
const decoded = JSON.parse(Buffer.from(paymentRequiredHeader, "base64").toString());
|
|
602
|
+
const accept = decoded.accepts?.[0];
|
|
603
|
+
if (accept) {
|
|
604
|
+
const amount = accept.amount || accept.maxAmountRequired || "0";
|
|
605
|
+
const decimals = accept.extra?.decimals || 6;
|
|
606
|
+
price = (Number(amount) / Math.pow(10, decimals)).toFixed(decimals > 4 ? 4 : 2);
|
|
607
|
+
network = accept.network || "";
|
|
608
|
+
}
|
|
609
|
+
if (decoded.resource?.description) {
|
|
610
|
+
description = decoded.resource.description;
|
|
611
|
+
}
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
const chainName = network.includes("solana") ? "Solana" : network.includes("eip155") ? "Base" : "";
|
|
615
|
+
const endpointSection = config.showEndpoint ? `<div class="endpoint"><code>${method} ${requestUrl}</code></div>` : "";
|
|
616
|
+
return `<!DOCTYPE html>
|
|
617
|
+
<html lang="en">
|
|
618
|
+
<head>
|
|
619
|
+
<meta charset="utf-8">
|
|
620
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
621
|
+
<title>${config.title} \u2014 $${price} USDC</title>
|
|
622
|
+
<style>${DEXTER_STYLES}${PAY_BUTTON_STYLES}</style>
|
|
623
|
+
</head>
|
|
624
|
+
<body>
|
|
625
|
+
<div class="card">
|
|
626
|
+
<div class="crest">${DEXTER_CREST_SVG}</div>
|
|
627
|
+
<h1>${config.title}</h1>
|
|
628
|
+
<p class="desc">${description}</p>
|
|
629
|
+
<div class="price">$<span id="price-value">${price}</span> USDC</div>
|
|
630
|
+
<div class="chain">${chainName}${chainName ? " network" : ""}</div>
|
|
631
|
+
${endpointSection}
|
|
632
|
+
|
|
633
|
+
<div id="pay-section" class="pay-section" style="display:none">
|
|
634
|
+
<button id="pay-btn" class="pay-btn">Pay $${price}</button>
|
|
635
|
+
<div id="pay-status" class="pay-status"></div>
|
|
636
|
+
<div class="pay-alt">or use <a href="${config.sdkUrl}">x402 SDK</a> for programmatic access</div>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
<div id="no-wallet" class="no-wallet" style="display:none">
|
|
640
|
+
<div class="info">
|
|
641
|
+
<strong>Access this endpoint:</strong><br><br>
|
|
642
|
+
Use any x402-compatible client or a browser with a Solana wallet extension (Phantom, Solflare, Backpack).<br><br>
|
|
643
|
+
<code>npm install @dexterai/x402</code><br><br>
|
|
644
|
+
<a href="${config.sdkUrl}">x402 SDK docs →</a>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
<div class="footer">
|
|
649
|
+
<a href="https://x402.org">x402</a>
|
|
650
|
+
<span class="sep"></span>
|
|
651
|
+
<a href="https://dexter.cash">Dexter</a>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
|
|
655
|
+
<div id="x402-data" style="display:none"
|
|
656
|
+
data-requirements="${paymentRequiredHeader}"
|
|
657
|
+
data-method="${method}"
|
|
658
|
+
data-url="${requestUrl}"
|
|
659
|
+
data-rpc="${rpcUrl}"
|
|
660
|
+
></div>
|
|
661
|
+
${PAY_SCRIPT}
|
|
662
|
+
</body>
|
|
663
|
+
</html>`;
|
|
664
|
+
}
|
|
665
|
+
function x402BrowserSupport(config = {}) {
|
|
666
|
+
const resolvedConfig = {
|
|
667
|
+
title: config.title ?? "Payment Required",
|
|
668
|
+
branding: config.branding ?? 'Powered by <a href="https://x402.org">x402</a>',
|
|
669
|
+
sdkUrl: config.sdkUrl ?? "https://x402.org",
|
|
670
|
+
showEndpoint: config.showEndpoint ?? true
|
|
671
|
+
};
|
|
672
|
+
const rpcUrl = config.rpcUrl ?? "https://api.dexter.cash/api/solana/rpc";
|
|
673
|
+
return (req, res, next) => {
|
|
674
|
+
const originalJson = res.json.bind(res);
|
|
675
|
+
res.json = function(body) {
|
|
676
|
+
if (res.statusCode === 402 && req.accepts("html") && !req.headers["payment-signature"]) {
|
|
677
|
+
const paymentRequired = res.getHeader("PAYMENT-REQUIRED") || res.getHeader("payment-required");
|
|
678
|
+
if (paymentRequired && typeof paymentRequired === "string") {
|
|
679
|
+
const html = generatePaywallHtml(
|
|
680
|
+
paymentRequired,
|
|
681
|
+
req.originalUrl,
|
|
682
|
+
req.method,
|
|
683
|
+
resolvedConfig,
|
|
684
|
+
rpcUrl
|
|
685
|
+
);
|
|
686
|
+
res.status(402).type("html").send(html);
|
|
687
|
+
return res;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return originalJson(body);
|
|
691
|
+
};
|
|
692
|
+
next();
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
360
696
|
// src/server/access-pass.ts
|
|
361
697
|
import crypto from "crypto";
|
|
362
698
|
var DURATION_REGEX = /^(\d+)(m|h|d|w)$/;
|
|
@@ -1396,6 +1732,7 @@ export {
|
|
|
1396
1732
|
isValidModel,
|
|
1397
1733
|
isValidModelId,
|
|
1398
1734
|
x402AccessPass,
|
|
1735
|
+
x402BrowserSupport,
|
|
1399
1736
|
x402Middleware
|
|
1400
1737
|
};
|
|
1401
1738
|
//# sourceMappingURL=index.js.map
|