@dexterai/x402 1.2.0 → 1.2.2
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 +47 -9
- package/dist/server/index.cjs +17 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +33 -3
- package/dist/server/index.d.ts +33 -3
- package/dist/server/index.js +17 -2
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,15 +30,17 @@ This SDK handles the entire flow automatically—you just call `fetch()` and pay
|
|
|
30
30
|
|
|
31
31
|
## Why This SDK?
|
|
32
32
|
|
|
33
|
-
**
|
|
33
|
+
**Monetize any API in minutes.** Add payments to your server in ~10 lines. Clients pay automatically—no checkout pages, no subscriptions, no invoices. Just HTTP.
|
|
34
34
|
|
|
35
|
-
**
|
|
35
|
+
**Dynamic pricing.** Charge based on usage: characters, tokens, records, pixels, API calls—whatever makes sense. Price scales with input, not fixed rates.
|
|
36
36
|
|
|
37
|
-
**Built-in
|
|
37
|
+
**Token-accurate LLM pricing.** Built-in [tiktoken](https://github.com/openai/tiktoken) support prices AI requests by actual token count. Works with OpenAI models out of the box, or bring your own rates for Anthropic, Gemini, Mistral, or local models.
|
|
38
38
|
|
|
39
|
-
**
|
|
39
|
+
**Full-stack.** Client SDK for browsers, server SDK for backends. React hooks, Express middleware patterns, facilitator client—everything you need.
|
|
40
40
|
|
|
41
|
-
**
|
|
41
|
+
**Multi-chain.** Solana and Base (Ethereum L2) with the same API. Add wallets for both and the SDK picks the right one automatically.
|
|
42
|
+
|
|
43
|
+
**Works out of the box.** Built-in RPC proxy, pre-flight balance checks, automatic retry on 402. Uses the [Dexter facilitator](https://x402.dexter.cash) by default—the only x402 facilitator with full Phantom wallet support on Solana mainnet.
|
|
42
44
|
|
|
43
45
|
---
|
|
44
46
|
|
|
@@ -181,13 +183,19 @@ app.post('/protected', async (req, res) => {
|
|
|
181
183
|
});
|
|
182
184
|
```
|
|
183
185
|
|
|
184
|
-
|
|
186
|
+
*Client SDK, React hook, and pricing utilities are production-verified at [dexter.cash/sdk](https://dexter.cash/sdk). `createX402Server` is a convenience wrapper not yet used in production.*
|
|
185
187
|
|
|
186
188
|
---
|
|
187
189
|
|
|
188
190
|
## Dynamic Pricing
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
**Generic pricing for any use case** - charge by characters, bytes, API calls, or any unit you define. No external dependencies.
|
|
193
|
+
|
|
194
|
+
Works for:
|
|
195
|
+
- LLM/AI endpoints (by character count)
|
|
196
|
+
- Image processing (by pixel count or file size)
|
|
197
|
+
- Data APIs (by record count)
|
|
198
|
+
- Any service where cost scales with input
|
|
191
199
|
|
|
192
200
|
```typescript
|
|
193
201
|
import { createX402Server, createDynamicPricing } from '@dexterai/x402/server';
|
|
@@ -235,7 +243,7 @@ The client SDK automatically forwards `X-Quote-Hash` on retry.
|
|
|
235
243
|
|
|
236
244
|
## Token Pricing (LLM-Accurate)
|
|
237
245
|
|
|
238
|
-
|
|
246
|
+
**Accurate token-based pricing for LLMs.** Uses tiktoken for token counting. Supports OpenAI models out of the box, plus custom rates for Anthropic, Gemini, Mistral, or any model.
|
|
239
247
|
|
|
240
248
|
```typescript
|
|
241
249
|
import { createX402Server, createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';
|
|
@@ -304,7 +312,37 @@ MODEL_PRICING['gpt-4o-mini'];
|
|
|
304
312
|
// → { input: 0.15, output: 0.6, maxTokens: 4096, tier: 'fast' }
|
|
305
313
|
```
|
|
306
314
|
|
|
307
|
-
**Supported tiers:** `fast`, `standard`, `reasoning`, `premium`
|
|
315
|
+
**Supported tiers:** `fast`, `standard`, `reasoning`, `premium`, `custom`
|
|
316
|
+
|
|
317
|
+
### Custom Models (Anthropic, Gemini, etc.)
|
|
318
|
+
|
|
319
|
+
Not using OpenAI? Pass your own rates:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// Anthropic Claude
|
|
323
|
+
const pricing = createTokenPricing({
|
|
324
|
+
model: 'claude-3-sonnet',
|
|
325
|
+
inputRate: 3.0, // $3.00 per 1M input tokens
|
|
326
|
+
outputRate: 15.0, // $15.00 per 1M output tokens
|
|
327
|
+
maxTokens: 4096,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Google Gemini
|
|
331
|
+
const pricing = createTokenPricing({
|
|
332
|
+
model: 'gemini-1.5-pro',
|
|
333
|
+
inputRate: 1.25,
|
|
334
|
+
outputRate: 5.0,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Custom/local model with custom tokenizer
|
|
338
|
+
const pricing = createTokenPricing({
|
|
339
|
+
model: 'llama-3-70b',
|
|
340
|
+
inputRate: 0.50,
|
|
341
|
+
tokenizer: (text) => llamaTokenizer.encode(text).length,
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
tiktoken's default encoding works well for most transformer models. Only use a custom tokenizer if your model has significantly different tokenization.
|
|
308
346
|
|
|
309
347
|
---
|
|
310
348
|
|
package/dist/server/index.cjs
CHANGED
|
@@ -507,16 +507,31 @@ function generateQuoteHash(prompt, model, rate, tokens) {
|
|
|
507
507
|
return (0, import_crypto.createHash)("sha256").update(prompt + configString).digest("hex").slice(0, 16);
|
|
508
508
|
}
|
|
509
509
|
function createTokenPricing(config = {}) {
|
|
510
|
-
const model = config.model
|
|
511
|
-
const
|
|
510
|
+
const model = config.model ?? DEFAULT_MODEL;
|
|
511
|
+
const builtInPricing = MODEL_PRICING[model];
|
|
512
|
+
const modelInfo = {
|
|
513
|
+
input: config.inputRate ?? builtInPricing?.input ?? MODEL_PRICING[DEFAULT_MODEL].input,
|
|
514
|
+
output: config.outputRate ?? builtInPricing?.output ?? MODEL_PRICING[DEFAULT_MODEL].output,
|
|
515
|
+
maxTokens: config.maxTokens ?? builtInPricing?.maxTokens ?? 4096,
|
|
516
|
+
tier: config.tier ?? builtInPricing?.tier ?? "custom"
|
|
517
|
+
};
|
|
518
|
+
const customTokenizer = config.tokenizer;
|
|
512
519
|
const fullConfig = {
|
|
513
520
|
model,
|
|
521
|
+
inputRate: modelInfo.input,
|
|
522
|
+
outputRate: modelInfo.output,
|
|
523
|
+
maxTokens: modelInfo.maxTokens,
|
|
524
|
+
tier: modelInfo.tier,
|
|
525
|
+
tokenizer: customTokenizer ?? ((text) => countTokens(text, model)),
|
|
514
526
|
minUsd: config.minUsd ?? 1e-3,
|
|
515
527
|
maxUsd: config.maxUsd ?? 50,
|
|
516
528
|
decimals: config.decimals ?? 6
|
|
517
529
|
};
|
|
518
530
|
const { minUsd, maxUsd, decimals } = fullConfig;
|
|
519
531
|
function countTokensInternal(input) {
|
|
532
|
+
if (customTokenizer) {
|
|
533
|
+
return customTokenizer(input);
|
|
534
|
+
}
|
|
520
535
|
return countTokens(input, model);
|
|
521
536
|
}
|
|
522
537
|
function calculate(input, systemPrompt) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/index.ts","../../src/types.ts","../../src/utils.ts","../../src/server/facilitator-client.ts","../../src/server/x402-server.ts","../../src/server/dynamic-pricing.ts","../../src/server/token-pricing.ts"],"sourcesContent":["/**\n * @dexterai/x402 Server\n *\n * Server-side helpers for accepting x402 payments.\n * Works with any x402 v2 facilitator.\n *\n * @example\n * ```typescript\n * import { createX402Server } from '@dexterai/x402/server';\n *\n * // Create server for Solana payments\n * const solanaServer = createX402Server({\n * payTo: 'YourSolanaAddress...',\n * network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n * });\n *\n * // Create server for Base payments\n * const baseServer = createX402Server({\n * payTo: '0xYourEvmAddress...',\n * network: 'eip155:8453',\n * asset: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 },\n * });\n *\n * // In your Express handler:\n * app.post('/protected', async (req, res) => {\n * const paymentSig = req.headers['payment-signature'];\n *\n * if (!paymentSig) {\n * const requirements = await solanaServer.buildRequirements({\n * amountAtomic: '50000',\n * resourceUrl: req.originalUrl,\n * });\n * res.setHeader('PAYMENT-REQUIRED', solanaServer.encodeRequirements(requirements));\n * return res.status(402).json({});\n * }\n *\n * const result = await solanaServer.settlePayment(paymentSig);\n * if (!result.success) {\n * return res.status(402).json({ error: result.errorReason });\n * }\n *\n * res.json({ data: 'protected content', transaction: result.transaction });\n * });\n * ```\n */\n\nexport { createX402Server } from './x402-server';\nexport type {\n X402ServerConfig,\n X402Server,\n BuildRequirementsOptions,\n AssetConfig,\n} from './x402-server';\n\nexport { FacilitatorClient, type SupportedKind, type SupportedResponse } from './facilitator-client';\n\n// Dynamic pricing (character-based)\nexport { createDynamicPricing, formatPricing } from './dynamic-pricing';\nexport type { DynamicPricingConfig, DynamicPricing, PriceQuote } from './dynamic-pricing';\n\n// Token pricing (LLM-accurate with tiktoken)\nexport { \n createTokenPricing, \n countTokens, \n getAvailableModels, \n isValidModel, \n formatTokenPricing,\n MODEL_PRICING,\n} from './token-pricing';\nexport type { \n TokenPricingConfig, \n TokenPricing, \n TokenPriceQuote, \n ModelPricing,\n} from './token-pricing';\n\n// Re-export types for convenience\nexport type { VerifyResponse, SettleResponse, PaymentRequired, PaymentAccept } from '../types';\nexport { DEXTER_FACILITATOR_URL, SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK, USDC_MINT, USDC_BASE } from '../types';\n","/**\n * x402 v2 SDK — Shared Types\n *\n * Chain-agnostic types for x402 v2 payments.\n * Works with Solana, Base, and any future x402-compatible networks.\n */\n\n// ============================================================================\n// Network Constants\n// ============================================================================\n\n/** CAIP-2 network identifier for Solana mainnet */\nexport const SOLANA_MAINNET_NETWORK = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';\n\n/** CAIP-2 network identifier for Base mainnet */\nexport const BASE_MAINNET_NETWORK = 'eip155:8453';\n\n/** Alias for Solana mainnet */\nexport const SOLANA_MAINNET = SOLANA_MAINNET_NETWORK;\n\n/** Alias for Base mainnet */\nexport const BASE_MAINNET = BASE_MAINNET_NETWORK;\n\n// ============================================================================\n// Asset Constants\n// ============================================================================\n\n/** USDC mint on Solana mainnet */\nexport const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\n\n/** USDC address on Base mainnet */\nexport const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';\n\n// ============================================================================\n// Facilitator Constants\n// ============================================================================\n\n/** Dexter's public x402 v2 facilitator URL */\nexport const DEXTER_FACILITATOR_URL = 'https://x402.dexter.cash';\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/**\n * Asset configuration for payments\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n /** Optional: Human-readable symbol */\n symbol?: string;\n}\n\n/**\n * Resource info included in payment requirements\n */\nexport interface ResourceInfo {\n /** Resource URL */\n url: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the resource */\n mimeType?: string;\n}\n\n/**\n * Extra fields in payment requirements\n * Chain-specific fields may vary\n */\nexport interface AcceptsExtra {\n /** Facilitator address that pays tx fees (required for Solana) */\n feePayer?: string;\n /** Token decimals (optional - defaults to 6 for USDC) */\n decimals?: number;\n /** EIP-712: Token name (EVM only) */\n name?: string;\n /** EIP-712: Token version (EVM only) */\n version?: string;\n /** Additional chain-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * A single payment option in the accepts array\n */\nexport interface PaymentAccept {\n /** Payment scheme (always 'exact' for x402 v2) */\n scheme: 'exact';\n /** CAIP-2 network identifier */\n network: string;\n /** Payment amount in atomic units (x402 spec field - REQUIRED) */\n maxAmountRequired: string;\n /** Alias for maxAmountRequired (for convenience) */\n amount?: string;\n /** Token address */\n asset: string;\n /** Seller's address to receive payment */\n payTo: string;\n /** Maximum seconds until payment expires */\n maxTimeoutSeconds: number;\n /** Chain-specific extra data */\n extra: AcceptsExtra;\n}\n\n/**\n * Full PaymentRequired structure (sent in PAYMENT-REQUIRED header)\n */\nexport interface PaymentRequired {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** Available payment options */\n accepts: PaymentAccept[];\n /** Optional error message */\n error?: string;\n}\n\n/**\n * PaymentSignature structure (sent in PAYMENT-SIGNATURE header)\n */\nexport interface PaymentSignature {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** The payment option that was accepted */\n accepted: PaymentAccept;\n /** The signed payment */\n payload: {\n /** Signed transaction (base64 for Solana, JSON for EVM) */\n transaction: string;\n };\n}\n\n// ============================================================================\n// Facilitator Response Types\n// ============================================================================\n\n/**\n * Response from /verify endpoint\n */\nexport interface VerifyResponse {\n /** Whether the payment is valid */\n isValid: boolean;\n /** Reason for invalidity (if invalid) */\n invalidReason?: string;\n /** Payer address */\n payer?: string;\n}\n\n/**\n * Response from /settle endpoint\n */\nexport interface SettleResponse {\n /** Whether settlement succeeded */\n success: boolean;\n /** Transaction signature/hash */\n transaction?: string;\n /** Network the payment was made on */\n network: string;\n /** Error reason (if failed) */\n errorReason?: string;\n /** Error code (if failed) */\n errorCode?: string;\n /** Payer address */\n payer?: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * SDK error codes\n */\nexport type X402ErrorCode =\n // Client errors\n | 'missing_payment_required_header'\n | 'invalid_payment_required'\n | 'unsupported_network'\n | 'no_matching_payment_option'\n | 'no_solana_accept' // Legacy, kept for compatibility\n | 'missing_fee_payer'\n | 'missing_decimals'\n | 'missing_amount'\n | 'amount_exceeds_max'\n | 'insufficient_balance'\n | 'wallet_missing_sign_transaction'\n | 'wallet_not_connected'\n | 'transaction_build_failed'\n | 'payment_rejected'\n // Server errors\n | 'invalid_payment_signature'\n | 'facilitator_verify_failed'\n | 'facilitator_settle_failed'\n | 'facilitator_request_failed'\n | 'no_matching_requirement';\n\n/**\n * Custom error class for x402 operations\n */\nexport class X402Error extends Error {\n /** Error code for programmatic handling */\n code: X402ErrorCode;\n /** Additional error details */\n details?: unknown;\n\n constructor(code: X402ErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'X402Error';\n this.code = code;\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, X402Error.prototype);\n }\n}\n","/**\n * Utility Functions\n *\n * Chain-agnostic helpers for x402 payments.\n */\n\nimport { SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK } from './types';\n\n// ============================================================================\n// Amount Conversion\n// ============================================================================\n\n/**\n * Convert human-readable amount to atomic units\n *\n * @param amount - Human-readable amount (e.g., 0.05 for $0.05)\n * @param decimals - Token decimals (e.g., 6 for USDC)\n * @returns Amount in atomic units as string\n *\n * @example\n * ```typescript\n * toAtomicUnits(0.05, 6) // '50000'\n * toAtomicUnits(1.50, 6) // '1500000'\n * ```\n */\nexport function toAtomicUnits(amount: number, decimals: number): string {\n const multiplier = Math.pow(10, decimals);\n return Math.floor(amount * multiplier).toString();\n}\n\n/**\n * Convert atomic units to human-readable amount\n *\n * @param atomicUnits - Amount in smallest units\n * @param decimals - Token decimals\n * @returns Human-readable amount\n *\n * @example\n * ```typescript\n * fromAtomicUnits('50000', 6) // 0.05\n * fromAtomicUnits(1500000n, 6) // 1.5\n * ```\n */\nexport function fromAtomicUnits(\n atomicUnits: string | bigint | number,\n decimals: number\n): number {\n const divisor = Math.pow(10, decimals);\n return Number(atomicUnits) / divisor;\n}\n\n// ============================================================================\n// Network Helpers\n// ============================================================================\n\n/**\n * Network type\n */\nexport type ChainFamily = 'solana' | 'evm' | 'unknown';\n\n/**\n * Get the chain family from a CAIP-2 network identifier\n *\n * @param network - CAIP-2 network identifier\n * @returns Chain family\n *\n * @example\n * ```typescript\n * getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'\n * getChainFamily('eip155:8453') // 'evm'\n * ```\n */\nexport function getChainFamily(network: string): ChainFamily {\n if (network.startsWith('solana:') || network === 'solana') {\n return 'solana';\n }\n if (network.startsWith('eip155:') || ['base', 'ethereum', 'arbitrum'].includes(network)) {\n return 'evm';\n }\n return 'unknown';\n}\n\n/**\n * Get default RPC URL for a network\n *\n * @param network - CAIP-2 network identifier\n * @returns Default RPC URL\n */\nexport function getDefaultRpcUrl(network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n if (network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1') {\n return 'https://api.devnet.solana.com';\n }\n if (network.includes('testnet') || network === 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z') {\n return 'https://api.testnet.solana.com';\n }\n // Mainnet uses Dexter's RPC proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n }\n\n if (family === 'evm') {\n // Extract chain ID from CAIP-2\n if (network.startsWith('eip155:')) {\n const chainId = network.split(':')[1];\n switch (chainId) {\n case '8453': return 'https://api.dexter.cash/api/base/rpc'; // Dexter proxy\n case '84532': return 'https://sepolia.base.org';\n case '1': return 'https://eth.llamarpc.com';\n case '42161': return 'https://arb1.arbitrum.io/rpc';\n default: return 'https://api.dexter.cash/api/base/rpc';\n }\n }\n // Legacy names\n if (network === 'base') return 'https://api.dexter.cash/api/base/rpc';\n if (network === 'ethereum') return 'https://eth.llamarpc.com';\n if (network === 'arbitrum') return 'https://arb1.arbitrum.io/rpc';\n return 'https://api.dexter.cash/api/base/rpc';\n }\n\n // Unknown - return Dexter's Solana proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n}\n\n/**\n * Get human-readable chain name\n *\n * @param network - CAIP-2 network identifier\n * @returns Human-readable name\n */\nexport function getChainName(network: string): string {\n const mapping: Record<string, string> = {\n [SOLANA_MAINNET_NETWORK]: 'Solana',\n 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'Solana Devnet',\n 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'Solana Testnet',\n 'solana': 'Solana',\n [BASE_MAINNET_NETWORK]: 'Base',\n 'eip155:84532': 'Base Sepolia',\n 'eip155:1': 'Ethereum',\n 'eip155:42161': 'Arbitrum One',\n 'base': 'Base',\n 'ethereum': 'Ethereum',\n 'arbitrum': 'Arbitrum',\n };\n return mapping[network] || network;\n}\n\n// ============================================================================\n// Transaction URL Helpers\n// ============================================================================\n\n/**\n * Get explorer URL for a transaction\n *\n * @param txSignature - Transaction signature/hash\n * @param network - CAIP-2 network identifier\n * @returns Explorer URL\n */\nexport function getExplorerUrl(txSignature: string, network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n const isDevnet = network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';\n if (isDevnet) {\n return `https://solscan.io/tx/${txSignature}?cluster=devnet`;\n }\n // Prefer Orb Markets for mainnet\n return `https://www.orbmarkets.io/tx/${txSignature}`;\n }\n\n if (family === 'evm') {\n // Extract chain ID\n let chainId = '8453'; // Default to Base\n if (network.startsWith('eip155:')) {\n chainId = network.split(':')[1];\n } else if (network === 'ethereum') {\n chainId = '1';\n } else if (network === 'arbitrum') {\n chainId = '42161';\n }\n\n switch (chainId) {\n case '8453': return `https://basescan.org/tx/${txSignature}`;\n case '84532': return `https://sepolia.basescan.org/tx/${txSignature}`;\n case '1': return `https://etherscan.io/tx/${txSignature}`;\n case '42161': return `https://arbiscan.io/tx/${txSignature}`;\n default: return `https://basescan.org/tx/${txSignature}`;\n }\n }\n\n return `https://solscan.io/tx/${txSignature}`;\n}\n\n// ============================================================================\n// Encoding Helpers\n// ============================================================================\n\n/**\n * Encode an object as base64 JSON\n */\nexport function encodeBase64Json(obj: unknown): string {\n return btoa(JSON.stringify(obj));\n}\n\n/**\n * Decode base64 JSON to object\n */\nexport function decodeBase64Json<T>(encoded: string): T {\n return JSON.parse(atob(encoded)) as T;\n}\n","/**\n * Facilitator Client\n *\n * Communicates with the x402 facilitator for:\n * - /supported - Get supported payment schemes and fee payer addresses\n * - /verify - Verify a payment signature before processing\n * - /settle - Submit the payment for execution\n *\n * Works with any x402 v2 facilitator (Dexter or others).\n */\n\nimport type { PaymentAccept, PaymentSignature, VerifyResponse, SettleResponse } from '../types';\nimport { DEXTER_FACILITATOR_URL } from '../types';\nimport { decodeBase64Json } from '../utils';\n\n/**\n * Supported payment kind from facilitator /supported endpoint\n */\nexport interface SupportedKind {\n x402Version: number;\n scheme: string;\n network: string;\n extra?: {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Response from facilitator /supported endpoint\n */\nexport interface SupportedResponse {\n kinds: SupportedKind[];\n}\n\n/**\n * Client for communicating with an x402 v2 facilitator\n */\nexport class FacilitatorClient {\n private facilitatorUrl: string;\n private cachedSupported: SupportedResponse | null = null;\n private cacheTime: number = 0;\n private readonly CACHE_TTL_MS = 60_000; // 1 minute cache\n\n constructor(facilitatorUrl: string = DEXTER_FACILITATOR_URL) {\n this.facilitatorUrl = facilitatorUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Get supported payment kinds from the facilitator\n * Results are cached for 1 minute to reduce network calls\n */\n async getSupported(): Promise<SupportedResponse> {\n const now = Date.now();\n if (this.cachedSupported && now - this.cacheTime < this.CACHE_TTL_MS) {\n return this.cachedSupported;\n }\n\n const response = await fetch(`${this.facilitatorUrl}/supported`);\n if (!response.ok) {\n throw new Error(`Facilitator /supported returned ${response.status}`);\n }\n\n this.cachedSupported = (await response.json()) as SupportedResponse;\n this.cacheTime = now;\n return this.cachedSupported;\n }\n\n /**\n * Get the fee payer address for a specific network\n *\n * @param network - CAIP-2 network identifier\n * @returns Fee payer address\n */\n async getFeePayer(network: string): Promise<string> {\n const supported = await this.getSupported();\n\n // Find matching network support (exact match or v2)\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n if (!kind?.extra?.feePayer) {\n throw new Error(\n `Facilitator does not support network \"${network}\" with scheme \"exact\", or feePayer not provided`\n );\n }\n\n return kind.extra.feePayer;\n }\n\n /**\n * Get extra data for a network (feePayer, decimals, EIP-712 data, etc.)\n *\n * @param network - CAIP-2 network identifier\n * @returns Extra data from /supported\n */\n async getNetworkExtra(network: string): Promise<SupportedKind['extra']> {\n const supported = await this.getSupported();\n\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n return kind?.extra;\n }\n\n /**\n * Verify a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Verification response\n */\n async verifyPayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<VerifyResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const verifyPayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(verifyPayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /verify returned ${response.status}:`, errorText);\n return {\n isValid: false,\n invalidReason: `facilitator_error_${response.status}`,\n };\n }\n\n return (await response.json()) as VerifyResponse;\n } catch (error) {\n console.error('Payment verification failed:', error);\n return {\n isValid: false,\n invalidReason: error instanceof Error ? error.message : 'unexpected_verify_error',\n };\n }\n }\n\n /**\n * Settle a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Settlement response with transaction signature on success\n */\n async settlePayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<SettleResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const settlePayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/settle`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(settlePayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /settle returned ${response.status}:`, errorText);\n return {\n success: false,\n network: requirements.network,\n errorReason: `facilitator_error_${response.status}`,\n };\n }\n\n const result = (await response.json()) as SettleResponse;\n return {\n ...result,\n network: requirements.network,\n };\n } catch (error) {\n console.error('Payment settlement failed:', error);\n return {\n success: false,\n network: requirements.network,\n errorReason: error instanceof Error ? error.message : 'unexpected_settle_error',\n };\n }\n }\n}\n","/**\n * x402 v2 Server\n *\n * Server-side helpers for accepting x402 payments.\n * Chain-agnostic - works with Solana, Base, and any x402-compatible network.\n *\n * @example\n * ```typescript\n * import { createX402Server } from '@dexterai/x402/server';\n *\n * const server = createX402Server({\n * payTo: 'YourAddress...',\n * network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n * });\n *\n * // Handle 402 responses\n * if (!paymentSignature) {\n * const requirements = await server.buildRequirements({\n * amountAtomic: '50000',\n * resourceUrl: '/api/protected',\n * });\n * res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));\n * res.status(402).json({});\n * return;\n * }\n *\n * // Verify and settle\n * const verify = await server.verifyPayment(paymentSignature);\n * if (!verify.isValid) throw new Error(verify.invalidReason);\n *\n * const settle = await server.settlePayment(paymentSignature);\n * if (!settle.success) throw new Error(settle.errorReason);\n *\n * // Payment successful!\n * res.json({ transaction: settle.transaction });\n * ```\n */\n\nimport type {\n PaymentRequired,\n PaymentAccept,\n ResourceInfo,\n AcceptsExtra,\n VerifyResponse,\n SettleResponse,\n} from '../types';\nimport {\n SOLANA_MAINNET_NETWORK,\n USDC_MINT,\n DEXTER_FACILITATOR_URL,\n} from '../types';\nimport { FacilitatorClient, type SupportedKind } from './facilitator-client';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Asset configuration\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n}\n\n/**\n * Server configuration\n */\nexport interface X402ServerConfig {\n /** Address to receive payments */\n payTo: string;\n /** Facilitator URL (defaults to Dexter) */\n facilitatorUrl?: string;\n /** CAIP-2 network identifier */\n network?: string;\n /** Asset configuration (defaults to USDC) */\n asset?: AssetConfig;\n /** Default payment timeout in seconds */\n defaultTimeoutSeconds?: number;\n}\n\n/**\n * Options for building payment requirements\n */\nexport interface BuildRequirementsOptions {\n /** Amount in atomic units (e.g., '50000' for 0.05 USDC) */\n amountAtomic: string;\n /** Full URL of the resource */\n resourceUrl: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the response */\n mimeType?: string;\n /** Override timeout for this request */\n timeoutSeconds?: number;\n}\n\n/**\n * x402 Server interface\n */\nexport interface X402Server {\n /** Build payment requirements (fetches feePayer from facilitator) */\n buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired>;\n\n /** Encode requirements for PAYMENT-REQUIRED header */\n encodeRequirements(requirements: PaymentRequired): string;\n\n /** Create complete 402 response object */\n create402Response(requirements: PaymentRequired): {\n status: 402;\n headers: { 'PAYMENT-REQUIRED': string };\n body: Record<string, unknown>;\n };\n\n /** Verify payment with facilitator */\n verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse>;\n\n /** Settle payment via facilitator */\n settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse>;\n\n /** Get PaymentAccept for verify/settle */\n getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept>;\n\n /** Get network this server is configured for */\n readonly network: string;\n\n /** Get facilitator client for advanced usage */\n readonly facilitator: FacilitatorClient;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Create an x402 server for accepting payments\n */\nexport function createX402Server(config: X402ServerConfig): X402Server {\n const {\n payTo,\n facilitatorUrl = DEXTER_FACILITATOR_URL,\n network = SOLANA_MAINNET_NETWORK,\n asset = { address: USDC_MINT, decimals: 6 },\n defaultTimeoutSeconds = 60,\n } = config;\n\n const facilitator = new FacilitatorClient(facilitatorUrl);\n\n // Cache for network extra data\n let cachedExtra: SupportedKind['extra'] | null = null;\n\n /**\n * Get extra data from facilitator (cached)\n */\n async function getNetworkExtra(): Promise<AcceptsExtra> {\n if (!cachedExtra) {\n cachedExtra = await facilitator.getNetworkExtra(network);\n }\n\n if (!cachedExtra?.feePayer) {\n throw new Error(`Facilitator does not provide feePayer for network \"${network}\"`);\n }\n\n return {\n feePayer: cachedExtra.feePayer,\n decimals: cachedExtra.decimals ?? asset.decimals,\n // Include any additional EIP-712 data for EVM chains\n name: cachedExtra.name,\n version: cachedExtra.version,\n };\n }\n\n /**\n * Build a PaymentAccept structure\n */\n async function getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept> {\n const {\n amountAtomic,\n timeoutSeconds = defaultTimeoutSeconds,\n } = options;\n\n const extra = await getNetworkExtra();\n\n return {\n scheme: 'exact',\n network,\n maxAmountRequired: amountAtomic,\n asset: asset.address,\n payTo,\n maxTimeoutSeconds: timeoutSeconds,\n extra,\n };\n }\n\n /**\n * Build payment requirements for a 402 response\n */\n async function buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired> {\n const {\n resourceUrl,\n description,\n mimeType = 'application/json',\n } = options;\n\n const resource: ResourceInfo = {\n url: resourceUrl,\n description,\n mimeType,\n };\n\n const accept = await getPaymentAccept(options);\n\n return {\n x402Version: 2,\n resource,\n accepts: [accept],\n error: 'Payment required',\n };\n }\n\n /**\n * Encode requirements for PAYMENT-REQUIRED header\n */\n function encodeRequirements(requirements: PaymentRequired): string {\n return btoa(JSON.stringify(requirements));\n }\n\n /**\n * Create complete 402 response object\n */\n function create402Response(requirements: PaymentRequired) {\n return {\n status: 402 as const,\n headers: {\n 'PAYMENT-REQUIRED': encodeRequirements(requirements),\n },\n body: {},\n };\n }\n\n /**\n * Verify payment with facilitator\n */\n async function verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse> {\n // If requirements not provided, build default\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.verifyPayment(paymentSignatureHeader, req);\n }\n\n /**\n * Settle payment via facilitator\n */\n async function settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse> {\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.settlePayment(paymentSignatureHeader, req);\n }\n\n return {\n buildRequirements,\n encodeRequirements,\n create402Response,\n verifyPayment,\n settlePayment,\n getPaymentAccept,\n network,\n facilitator,\n };\n}\n","/**\n * Dynamic Pricing for x402\n *\n * Calculate prices based on input length (characters, tokens, etc.)\n * Perfect for LLM/AI endpoints where cost scales with input size.\n *\n * @example\n * ```typescript\n * import { createDynamicPricing } from '@dexterai/x402/server';\n *\n * const pricing = createDynamicPricing({\n * unitSize: 1000, // chars per billing unit\n * ratePerUnit: 0.01, // $0.01 per unit\n * minUsd: 0.01, // floor\n * maxUsd: 10.00, // ceiling (optional)\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '23000', usdAmount: 0.023, quoteHash: 'abc...', units: 2.3 }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for dynamic pricing\n */\nexport interface DynamicPricingConfig {\n /**\n * Characters per billing unit.\n * Example: 1000 means every 1000 chars = 1 unit\n */\n unitSize: number;\n\n /**\n * USD per unit.\n * Example: 0.01 means $0.01 per unit\n */\n ratePerUnit: number;\n\n /**\n * Minimum USD amount (floor).\n * Recommended: 0.01 (practical minimum for settlement)\n * @default 0.01\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * Optional - prevents unexpectedly large bills.\n */\n maxUsd?: number;\n\n /**\n * Rounding mode for unit calculation.\n * - 'ceil': Always round up (fair to seller)\n * - 'floor': Always round down (fair to buyer)\n * - 'round': Standard rounding\n * @default 'ceil'\n */\n roundingMode?: 'ceil' | 'floor' | 'round';\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Price quote returned by calculate()\n */\nexport interface PriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount (for display) */\n usdAmount: number;\n\n /**\n * Quote hash for validation.\n * Includes input + config, so config changes invalidate quotes.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n\n /** Number of billing units */\n units: number;\n\n /** Input length in characters */\n inputLength: number;\n}\n\n/**\n * Dynamic pricing calculator\n */\nexport interface DynamicPricing {\n /** Calculate price from input */\n calculate(input: string): PriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Get pricing config (for display) */\n readonly config: Required<DynamicPricingConfig>;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Simple hash function (browser-compatible, no crypto needed)\n * Uses FNV-1a for speed and simplicity\n */\nfunction simpleHash(str: string): string {\n let hash = 2166136261; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 16777619); // FNV prime\n }\n // Convert to hex string\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Create a dynamic pricing calculator\n */\nexport function createDynamicPricing(config: DynamicPricingConfig): DynamicPricing {\n const fullConfig: Required<DynamicPricingConfig> = {\n unitSize: config.unitSize,\n ratePerUnit: config.ratePerUnit,\n minUsd: config.minUsd ?? 0.01,\n maxUsd: config.maxUsd ?? Infinity,\n roundingMode: config.roundingMode ?? 'ceil',\n decimals: config.decimals ?? 6,\n };\n\n const { unitSize, ratePerUnit, minUsd, maxUsd, roundingMode, decimals } = fullConfig;\n\n // Validate config\n if (unitSize <= 0) throw new Error('unitSize must be positive');\n if (ratePerUnit <= 0) throw new Error('ratePerUnit must be positive');\n if (minUsd < 0) throw new Error('minUsd cannot be negative');\n if (maxUsd < minUsd) throw new Error('maxUsd must be >= minUsd');\n\n /**\n * Build hash input string (input + config)\n * Config is included so pricing changes invalidate old quotes\n */\n function buildHashInput(input: string): string {\n const configStr = JSON.stringify({\n unitSize,\n ratePerUnit,\n minUsd,\n maxUsd: maxUsd === Infinity ? 'none' : maxUsd,\n roundingMode,\n });\n return `${input}|${configStr}`;\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string): PriceQuote {\n const inputLength = input.length;\n\n // Calculate units based on rounding mode\n const rawUnits = inputLength / unitSize;\n let units: number;\n switch (roundingMode) {\n case 'ceil':\n units = Math.ceil(rawUnits);\n break;\n case 'floor':\n units = Math.floor(rawUnits);\n break;\n case 'round':\n units = Math.round(rawUnits);\n break;\n }\n\n // Ensure at least 1 unit if there's any input\n if (inputLength > 0 && units === 0) {\n units = 1;\n }\n\n // Calculate USD amount\n let usdAmount = units * ratePerUnit;\n\n // Apply min/max\n usdAmount = Math.max(minUsd, usdAmount);\n usdAmount = Math.min(maxUsd, usdAmount);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash (includes config)\n const quoteHash = simpleHash(buildHashInput(input));\n\n return {\n amountAtomic,\n usdAmount,\n quoteHash,\n units,\n inputLength,\n };\n }\n\n /**\n * Validate quote hash\n * Returns true if the hash matches (input + config unchanged)\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const expectedHash = simpleHash(buildHashInput(input));\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n config: fullConfig,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Format pricing for display\n * Example: \"from $0.01 per 1,000 chars\"\n */\nexport function formatPricing(config: DynamicPricingConfig): string {\n const rate = config.ratePerUnit.toFixed(2);\n const units = config.unitSize.toLocaleString();\n return `from $${rate} per ${units} chars`;\n}\n\n\n\n","/**\n * Token-Based Pricing for x402\n *\n * Accurate LLM pricing using tiktoken for token counting.\n * Uses real OpenAI model rates for precise cost calculation.\n *\n * @example\n * ```typescript\n * import { createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';\n *\n * const pricing = createTokenPricing({\n * model: 'gpt-4o-mini',\n * // Optional overrides:\n * // minUsd: 0.001,\n * // maxUsd: 50.0,\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '1500', usdAmount: 0.0015, inputTokens: 100, quoteHash: 'abc...' }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\nimport { createHash } from 'crypto';\nimport { encoding_for_model, get_encoding, type TiktokenModel } from 'tiktoken';\n\n// ============================================================================\n// Model Pricing Table\n// ============================================================================\n\n/**\n * Pricing info for a model\n */\nexport interface ModelPricing {\n /** USD per 1M input tokens */\n input: number;\n /** USD per 1M output tokens */\n output: number;\n /** USD per 1M cached input tokens (optional) */\n cached?: number;\n /** Default max output tokens for this model */\n maxTokens: number;\n /** Pricing tier */\n tier: 'fast' | 'standard' | 'reasoning' | 'premium';\n}\n\n/**\n * OpenAI Model Pricing - USD per million tokens\n * Updated: December 2024\n */\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // === FAST TIER (cheapest, fastest) ===\n 'gpt-4o-mini': { \n input: 0.15, output: 0.6, cached: 0.075, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-mini': { \n input: 0.4, output: 1.6, cached: 0.1, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-nano': { \n input: 0.1, output: 0.4, cached: 0.025, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-nano': { \n input: 0.05, output: 0.4, cached: 0.005, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-mini': { \n input: 0.25, output: 2.0, cached: 0.025, \n maxTokens: 8192, tier: 'fast' \n },\n\n // === STANDARD TIER (balanced) ===\n 'gpt-4o': { \n input: 2.5, output: 10.0, cached: 1.25, \n maxTokens: 4096, tier: 'standard' \n },\n 'gpt-4.1': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 8192, tier: 'standard' \n },\n 'gpt-5': { \n input: 1.25, output: 10.0, cached: 0.125, \n maxTokens: 8192, tier: 'standard' \n },\n\n // === REASONING TIER (o-series) ===\n 'o1-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o4-mini': { \n input: 1.1, output: 4.4, cached: 0.275, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n 'o1': { \n input: 15.0, output: 60.0, cached: 7.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n\n // === PREMIUM TIER (expensive, specialized) ===\n 'o3-pro': { \n input: 20.0, output: 80.0, \n maxTokens: 32768, tier: 'premium' \n },\n 'o1-pro': { \n input: 150.0, output: 600.0, \n maxTokens: 32768, tier: 'premium' \n },\n};\n\n// Default model for fallback\nconst DEFAULT_MODEL = 'gpt-4o-mini';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for token-based pricing\n */\nexport interface TokenPricingConfig {\n /**\n * Model to use for pricing.\n * Must be a key in MODEL_PRICING or will fallback to gpt-4o-mini.\n */\n model?: string;\n\n /**\n * Minimum USD amount (floor).\n * @default 0.001\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * @default 50.0\n */\n maxUsd?: number;\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Token price quote\n */\nexport interface TokenPriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount */\n usdAmount: number;\n\n /** Number of input tokens */\n inputTokens: number;\n\n /** Model used for pricing */\n model: string;\n\n /** Pricing tier */\n tier: string;\n\n /** Input rate per million tokens */\n inputRatePerMillion: number;\n\n /** Output rate per million tokens */\n outputRatePerMillion: number;\n\n /** Max output tokens for this model */\n maxOutputTokens: number;\n\n /**\n * Quote hash for validation.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n}\n\n/**\n * Token pricing calculator\n */\nexport interface TokenPricing {\n /** Calculate price from input text */\n calculate(input: string, systemPrompt?: string): TokenPriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Count tokens in a string */\n countTokens(input: string): number;\n\n /** Get pricing config */\n readonly config: Required<TokenPricingConfig>;\n\n /** Get model info */\n readonly modelInfo: ModelPricing;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Get tiktoken encoding for a model.\n * Falls back to cl100k_base for unknown models.\n */\nfunction getEncodingForModel(model: string) {\n try {\n return encoding_for_model(model as TiktokenModel);\n } catch {\n // Fall back to cl100k_base (GPT-4/4o family encoding)\n return get_encoding('cl100k_base');\n }\n}\n\n/**\n * Count tokens in a string using tiktoken.\n */\nexport function countTokens(text: string, model: string = DEFAULT_MODEL): number {\n const encoding = getEncodingForModel(model);\n try {\n const tokens = encoding.encode(text);\n return tokens.length;\n } finally {\n encoding.free();\n }\n}\n\n/**\n * Generate a hash of the prompt + pricing config for validation.\n */\nfunction generateQuoteHash(prompt: string, model: string, rate: number, tokens: number): string {\n const configString = JSON.stringify({ model, rate, tokens });\n return createHash('sha256').update(prompt + configString).digest('hex').slice(0, 16);\n}\n\n/**\n * Create a token-based pricing calculator\n */\nexport function createTokenPricing(config: TokenPricingConfig = {}): TokenPricing {\n const model = config.model && MODEL_PRICING[config.model] ? config.model : DEFAULT_MODEL;\n const modelInfo = MODEL_PRICING[model];\n\n const fullConfig: Required<TokenPricingConfig> = {\n model,\n minUsd: config.minUsd ?? 0.001,\n maxUsd: config.maxUsd ?? 50.0,\n decimals: config.decimals ?? 6,\n };\n\n const { minUsd, maxUsd, decimals } = fullConfig;\n\n /**\n * Count tokens in text\n */\n function countTokensInternal(input: string): number {\n return countTokens(input, model);\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string, systemPrompt?: string): TokenPriceQuote {\n // Count input tokens (prompt + system prompt if provided)\n const fullInput = systemPrompt ? `${systemPrompt}\\n\\n${input}` : input;\n const inputTokens = countTokensInternal(fullInput);\n\n // Calculate USD cost based on input tokens only\n // Price = (inputTokens / 1,000,000) × inputRate\n let usdAmount = (inputTokens / 1_000_000) * modelInfo.input;\n\n // Apply min/max caps\n usdAmount = Math.max(usdAmount, minUsd);\n usdAmount = Math.min(usdAmount, maxUsd);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash for validation\n const quoteHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n\n return {\n amountAtomic,\n usdAmount,\n inputTokens,\n model,\n tier: modelInfo.tier,\n inputRatePerMillion: modelInfo.input,\n outputRatePerMillion: modelInfo.output,\n maxOutputTokens: modelInfo.maxTokens,\n quoteHash,\n };\n }\n\n /**\n * Validate quote hash\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const inputTokens = countTokensInternal(input);\n const expectedHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n countTokens: countTokensInternal,\n config: fullConfig,\n modelInfo,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Get list of available models with their pricing.\n */\nexport function getAvailableModels(): Array<{\n model: string;\n inputRate: number;\n outputRate: number;\n maxTokens: number;\n tier: string;\n}> {\n return Object.entries(MODEL_PRICING)\n .map(([model, pricing]) => ({\n model,\n inputRate: pricing.input,\n outputRate: pricing.output,\n maxTokens: pricing.maxTokens,\n tier: pricing.tier,\n }))\n .sort((a, b) => {\n // Sort by tier, then by input rate\n const tierOrder = { fast: 0, standard: 1, reasoning: 2, premium: 3 };\n const tierDiff = tierOrder[a.tier as keyof typeof tierOrder] - tierOrder[b.tier as keyof typeof tierOrder];\n if (tierDiff !== 0) return tierDiff;\n return a.inputRate - b.inputRate;\n });\n}\n\n/**\n * Check if a model exists in our pricing.\n */\nexport function isValidModel(model: string): boolean {\n return model in MODEL_PRICING;\n}\n\n/**\n * Format token pricing for display\n * Example: \"$0.15 per 1M tokens (gpt-4o-mini)\"\n */\nexport function formatTokenPricing(model: string = DEFAULT_MODEL): string {\n const pricing = MODEL_PRICING[model] ?? MODEL_PRICING[DEFAULT_MODEL];\n const actualModel = MODEL_PRICING[model] ? model : DEFAULT_MODEL;\n return `$${pricing.input.toFixed(2)} per 1M tokens (${actualModel})`;\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../src/types.ts","../../src/utils.ts","../../src/server/facilitator-client.ts","../../src/server/x402-server.ts","../../src/server/dynamic-pricing.ts","../../src/server/token-pricing.ts"],"sourcesContent":["/**\n * @dexterai/x402 Server\n *\n * Server-side helpers for accepting x402 payments.\n * Works with any x402 v2 facilitator.\n *\n * @example\n * ```typescript\n * import { createX402Server } from '@dexterai/x402/server';\n *\n * // Create server for Solana payments\n * const solanaServer = createX402Server({\n * payTo: 'YourSolanaAddress...',\n * network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n * });\n *\n * // Create server for Base payments\n * const baseServer = createX402Server({\n * payTo: '0xYourEvmAddress...',\n * network: 'eip155:8453',\n * asset: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 },\n * });\n *\n * // In your Express handler:\n * app.post('/protected', async (req, res) => {\n * const paymentSig = req.headers['payment-signature'];\n *\n * if (!paymentSig) {\n * const requirements = await solanaServer.buildRequirements({\n * amountAtomic: '50000',\n * resourceUrl: req.originalUrl,\n * });\n * res.setHeader('PAYMENT-REQUIRED', solanaServer.encodeRequirements(requirements));\n * return res.status(402).json({});\n * }\n *\n * const result = await solanaServer.settlePayment(paymentSig);\n * if (!result.success) {\n * return res.status(402).json({ error: result.errorReason });\n * }\n *\n * res.json({ data: 'protected content', transaction: result.transaction });\n * });\n * ```\n */\n\nexport { createX402Server } from './x402-server';\nexport type {\n X402ServerConfig,\n X402Server,\n BuildRequirementsOptions,\n AssetConfig,\n} from './x402-server';\n\nexport { FacilitatorClient, type SupportedKind, type SupportedResponse } from './facilitator-client';\n\n// Dynamic pricing (character-based)\nexport { createDynamicPricing, formatPricing } from './dynamic-pricing';\nexport type { DynamicPricingConfig, DynamicPricing, PriceQuote } from './dynamic-pricing';\n\n// Token pricing (LLM-accurate with tiktoken)\nexport { \n createTokenPricing, \n countTokens, \n getAvailableModels, \n isValidModel, \n formatTokenPricing,\n MODEL_PRICING,\n} from './token-pricing';\nexport type { \n TokenPricingConfig, \n TokenPricing, \n TokenPriceQuote, \n ModelPricing,\n} from './token-pricing';\n\n// Re-export types for convenience\nexport type { VerifyResponse, SettleResponse, PaymentRequired, PaymentAccept } from '../types';\nexport { DEXTER_FACILITATOR_URL, SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK, USDC_MINT, USDC_BASE } from '../types';\n","/**\n * x402 v2 SDK — Shared Types\n *\n * Chain-agnostic types for x402 v2 payments.\n * Works with Solana, Base, and any future x402-compatible networks.\n */\n\n// ============================================================================\n// Network Constants\n// ============================================================================\n\n/** CAIP-2 network identifier for Solana mainnet */\nexport const SOLANA_MAINNET_NETWORK = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';\n\n/** CAIP-2 network identifier for Base mainnet */\nexport const BASE_MAINNET_NETWORK = 'eip155:8453';\n\n/** Alias for Solana mainnet */\nexport const SOLANA_MAINNET = SOLANA_MAINNET_NETWORK;\n\n/** Alias for Base mainnet */\nexport const BASE_MAINNET = BASE_MAINNET_NETWORK;\n\n// ============================================================================\n// Asset Constants\n// ============================================================================\n\n/** USDC mint on Solana mainnet */\nexport const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\n\n/** USDC address on Base mainnet */\nexport const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';\n\n// ============================================================================\n// Facilitator Constants\n// ============================================================================\n\n/** Dexter's public x402 v2 facilitator URL */\nexport const DEXTER_FACILITATOR_URL = 'https://x402.dexter.cash';\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/**\n * Asset configuration for payments\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n /** Optional: Human-readable symbol */\n symbol?: string;\n}\n\n/**\n * Resource info included in payment requirements\n */\nexport interface ResourceInfo {\n /** Resource URL */\n url: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the resource */\n mimeType?: string;\n}\n\n/**\n * Extra fields in payment requirements\n * Chain-specific fields may vary\n */\nexport interface AcceptsExtra {\n /** Facilitator address that pays tx fees (required for Solana) */\n feePayer?: string;\n /** Token decimals (optional - defaults to 6 for USDC) */\n decimals?: number;\n /** EIP-712: Token name (EVM only) */\n name?: string;\n /** EIP-712: Token version (EVM only) */\n version?: string;\n /** Additional chain-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * A single payment option in the accepts array\n */\nexport interface PaymentAccept {\n /** Payment scheme (always 'exact' for x402 v2) */\n scheme: 'exact';\n /** CAIP-2 network identifier */\n network: string;\n /** Payment amount in atomic units (x402 spec field - REQUIRED) */\n maxAmountRequired: string;\n /** Alias for maxAmountRequired (for convenience) */\n amount?: string;\n /** Token address */\n asset: string;\n /** Seller's address to receive payment */\n payTo: string;\n /** Maximum seconds until payment expires */\n maxTimeoutSeconds: number;\n /** Chain-specific extra data */\n extra: AcceptsExtra;\n}\n\n/**\n * Full PaymentRequired structure (sent in PAYMENT-REQUIRED header)\n */\nexport interface PaymentRequired {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** Available payment options */\n accepts: PaymentAccept[];\n /** Optional error message */\n error?: string;\n}\n\n/**\n * PaymentSignature structure (sent in PAYMENT-SIGNATURE header)\n */\nexport interface PaymentSignature {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** The payment option that was accepted */\n accepted: PaymentAccept;\n /** The signed payment */\n payload: {\n /** Signed transaction (base64 for Solana, JSON for EVM) */\n transaction: string;\n };\n}\n\n// ============================================================================\n// Facilitator Response Types\n// ============================================================================\n\n/**\n * Response from /verify endpoint\n */\nexport interface VerifyResponse {\n /** Whether the payment is valid */\n isValid: boolean;\n /** Reason for invalidity (if invalid) */\n invalidReason?: string;\n /** Payer address */\n payer?: string;\n}\n\n/**\n * Response from /settle endpoint\n */\nexport interface SettleResponse {\n /** Whether settlement succeeded */\n success: boolean;\n /** Transaction signature/hash */\n transaction?: string;\n /** Network the payment was made on */\n network: string;\n /** Error reason (if failed) */\n errorReason?: string;\n /** Error code (if failed) */\n errorCode?: string;\n /** Payer address */\n payer?: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * SDK error codes\n */\nexport type X402ErrorCode =\n // Client errors\n | 'missing_payment_required_header'\n | 'invalid_payment_required'\n | 'unsupported_network'\n | 'no_matching_payment_option'\n | 'no_solana_accept' // Legacy, kept for compatibility\n | 'missing_fee_payer'\n | 'missing_decimals'\n | 'missing_amount'\n | 'amount_exceeds_max'\n | 'insufficient_balance'\n | 'wallet_missing_sign_transaction'\n | 'wallet_not_connected'\n | 'transaction_build_failed'\n | 'payment_rejected'\n // Server errors\n | 'invalid_payment_signature'\n | 'facilitator_verify_failed'\n | 'facilitator_settle_failed'\n | 'facilitator_request_failed'\n | 'no_matching_requirement';\n\n/**\n * Custom error class for x402 operations\n */\nexport class X402Error extends Error {\n /** Error code for programmatic handling */\n code: X402ErrorCode;\n /** Additional error details */\n details?: unknown;\n\n constructor(code: X402ErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'X402Error';\n this.code = code;\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, X402Error.prototype);\n }\n}\n","/**\n * Utility Functions\n *\n * Chain-agnostic helpers for x402 payments.\n */\n\nimport { SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK } from './types';\n\n// ============================================================================\n// Amount Conversion\n// ============================================================================\n\n/**\n * Convert human-readable amount to atomic units\n *\n * @param amount - Human-readable amount (e.g., 0.05 for $0.05)\n * @param decimals - Token decimals (e.g., 6 for USDC)\n * @returns Amount in atomic units as string\n *\n * @example\n * ```typescript\n * toAtomicUnits(0.05, 6) // '50000'\n * toAtomicUnits(1.50, 6) // '1500000'\n * ```\n */\nexport function toAtomicUnits(amount: number, decimals: number): string {\n const multiplier = Math.pow(10, decimals);\n return Math.floor(amount * multiplier).toString();\n}\n\n/**\n * Convert atomic units to human-readable amount\n *\n * @param atomicUnits - Amount in smallest units\n * @param decimals - Token decimals\n * @returns Human-readable amount\n *\n * @example\n * ```typescript\n * fromAtomicUnits('50000', 6) // 0.05\n * fromAtomicUnits(1500000n, 6) // 1.5\n * ```\n */\nexport function fromAtomicUnits(\n atomicUnits: string | bigint | number,\n decimals: number\n): number {\n const divisor = Math.pow(10, decimals);\n return Number(atomicUnits) / divisor;\n}\n\n// ============================================================================\n// Network Helpers\n// ============================================================================\n\n/**\n * Network type\n */\nexport type ChainFamily = 'solana' | 'evm' | 'unknown';\n\n/**\n * Get the chain family from a CAIP-2 network identifier\n *\n * @param network - CAIP-2 network identifier\n * @returns Chain family\n *\n * @example\n * ```typescript\n * getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'\n * getChainFamily('eip155:8453') // 'evm'\n * ```\n */\nexport function getChainFamily(network: string): ChainFamily {\n if (network.startsWith('solana:') || network === 'solana') {\n return 'solana';\n }\n if (network.startsWith('eip155:') || ['base', 'ethereum', 'arbitrum'].includes(network)) {\n return 'evm';\n }\n return 'unknown';\n}\n\n/**\n * Get default RPC URL for a network\n *\n * @param network - CAIP-2 network identifier\n * @returns Default RPC URL\n */\nexport function getDefaultRpcUrl(network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n if (network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1') {\n return 'https://api.devnet.solana.com';\n }\n if (network.includes('testnet') || network === 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z') {\n return 'https://api.testnet.solana.com';\n }\n // Mainnet uses Dexter's RPC proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n }\n\n if (family === 'evm') {\n // Extract chain ID from CAIP-2\n if (network.startsWith('eip155:')) {\n const chainId = network.split(':')[1];\n switch (chainId) {\n case '8453': return 'https://api.dexter.cash/api/base/rpc'; // Dexter proxy\n case '84532': return 'https://sepolia.base.org';\n case '1': return 'https://eth.llamarpc.com';\n case '42161': return 'https://arb1.arbitrum.io/rpc';\n default: return 'https://api.dexter.cash/api/base/rpc';\n }\n }\n // Legacy names\n if (network === 'base') return 'https://api.dexter.cash/api/base/rpc';\n if (network === 'ethereum') return 'https://eth.llamarpc.com';\n if (network === 'arbitrum') return 'https://arb1.arbitrum.io/rpc';\n return 'https://api.dexter.cash/api/base/rpc';\n }\n\n // Unknown - return Dexter's Solana proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n}\n\n/**\n * Get human-readable chain name\n *\n * @param network - CAIP-2 network identifier\n * @returns Human-readable name\n */\nexport function getChainName(network: string): string {\n const mapping: Record<string, string> = {\n [SOLANA_MAINNET_NETWORK]: 'Solana',\n 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'Solana Devnet',\n 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'Solana Testnet',\n 'solana': 'Solana',\n [BASE_MAINNET_NETWORK]: 'Base',\n 'eip155:84532': 'Base Sepolia',\n 'eip155:1': 'Ethereum',\n 'eip155:42161': 'Arbitrum One',\n 'base': 'Base',\n 'ethereum': 'Ethereum',\n 'arbitrum': 'Arbitrum',\n };\n return mapping[network] || network;\n}\n\n// ============================================================================\n// Transaction URL Helpers\n// ============================================================================\n\n/**\n * Get explorer URL for a transaction\n *\n * @param txSignature - Transaction signature/hash\n * @param network - CAIP-2 network identifier\n * @returns Explorer URL\n */\nexport function getExplorerUrl(txSignature: string, network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n const isDevnet = network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';\n if (isDevnet) {\n return `https://solscan.io/tx/${txSignature}?cluster=devnet`;\n }\n // Prefer Orb Markets for mainnet\n return `https://www.orbmarkets.io/tx/${txSignature}`;\n }\n\n if (family === 'evm') {\n // Extract chain ID\n let chainId = '8453'; // Default to Base\n if (network.startsWith('eip155:')) {\n chainId = network.split(':')[1];\n } else if (network === 'ethereum') {\n chainId = '1';\n } else if (network === 'arbitrum') {\n chainId = '42161';\n }\n\n switch (chainId) {\n case '8453': return `https://basescan.org/tx/${txSignature}`;\n case '84532': return `https://sepolia.basescan.org/tx/${txSignature}`;\n case '1': return `https://etherscan.io/tx/${txSignature}`;\n case '42161': return `https://arbiscan.io/tx/${txSignature}`;\n default: return `https://basescan.org/tx/${txSignature}`;\n }\n }\n\n return `https://solscan.io/tx/${txSignature}`;\n}\n\n// ============================================================================\n// Encoding Helpers\n// ============================================================================\n\n/**\n * Encode an object as base64 JSON\n */\nexport function encodeBase64Json(obj: unknown): string {\n return btoa(JSON.stringify(obj));\n}\n\n/**\n * Decode base64 JSON to object\n */\nexport function decodeBase64Json<T>(encoded: string): T {\n return JSON.parse(atob(encoded)) as T;\n}\n","/**\n * Facilitator Client\n *\n * Communicates with the x402 facilitator for:\n * - /supported - Get supported payment schemes and fee payer addresses\n * - /verify - Verify a payment signature before processing\n * - /settle - Submit the payment for execution\n *\n * Works with any x402 v2 facilitator (Dexter or others).\n */\n\nimport type { PaymentAccept, PaymentSignature, VerifyResponse, SettleResponse } from '../types';\nimport { DEXTER_FACILITATOR_URL } from '../types';\nimport { decodeBase64Json } from '../utils';\n\n/**\n * Supported payment kind from facilitator /supported endpoint\n */\nexport interface SupportedKind {\n x402Version: number;\n scheme: string;\n network: string;\n extra?: {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Response from facilitator /supported endpoint\n */\nexport interface SupportedResponse {\n kinds: SupportedKind[];\n}\n\n/**\n * Client for communicating with an x402 v2 facilitator\n */\nexport class FacilitatorClient {\n private facilitatorUrl: string;\n private cachedSupported: SupportedResponse | null = null;\n private cacheTime: number = 0;\n private readonly CACHE_TTL_MS = 60_000; // 1 minute cache\n\n constructor(facilitatorUrl: string = DEXTER_FACILITATOR_URL) {\n this.facilitatorUrl = facilitatorUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Get supported payment kinds from the facilitator\n * Results are cached for 1 minute to reduce network calls\n */\n async getSupported(): Promise<SupportedResponse> {\n const now = Date.now();\n if (this.cachedSupported && now - this.cacheTime < this.CACHE_TTL_MS) {\n return this.cachedSupported;\n }\n\n const response = await fetch(`${this.facilitatorUrl}/supported`);\n if (!response.ok) {\n throw new Error(`Facilitator /supported returned ${response.status}`);\n }\n\n this.cachedSupported = (await response.json()) as SupportedResponse;\n this.cacheTime = now;\n return this.cachedSupported;\n }\n\n /**\n * Get the fee payer address for a specific network\n *\n * @param network - CAIP-2 network identifier\n * @returns Fee payer address\n */\n async getFeePayer(network: string): Promise<string> {\n const supported = await this.getSupported();\n\n // Find matching network support (exact match or v2)\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n if (!kind?.extra?.feePayer) {\n throw new Error(\n `Facilitator does not support network \"${network}\" with scheme \"exact\", or feePayer not provided`\n );\n }\n\n return kind.extra.feePayer;\n }\n\n /**\n * Get extra data for a network (feePayer, decimals, EIP-712 data, etc.)\n *\n * @param network - CAIP-2 network identifier\n * @returns Extra data from /supported\n */\n async getNetworkExtra(network: string): Promise<SupportedKind['extra']> {\n const supported = await this.getSupported();\n\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n return kind?.extra;\n }\n\n /**\n * Verify a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Verification response\n */\n async verifyPayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<VerifyResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const verifyPayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(verifyPayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /verify returned ${response.status}:`, errorText);\n return {\n isValid: false,\n invalidReason: `facilitator_error_${response.status}`,\n };\n }\n\n return (await response.json()) as VerifyResponse;\n } catch (error) {\n console.error('Payment verification failed:', error);\n return {\n isValid: false,\n invalidReason: error instanceof Error ? error.message : 'unexpected_verify_error',\n };\n }\n }\n\n /**\n * Settle a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Settlement response with transaction signature on success\n */\n async settlePayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<SettleResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const settlePayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/settle`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(settlePayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /settle returned ${response.status}:`, errorText);\n return {\n success: false,\n network: requirements.network,\n errorReason: `facilitator_error_${response.status}`,\n };\n }\n\n const result = (await response.json()) as SettleResponse;\n return {\n ...result,\n network: requirements.network,\n };\n } catch (error) {\n console.error('Payment settlement failed:', error);\n return {\n success: false,\n network: requirements.network,\n errorReason: error instanceof Error ? error.message : 'unexpected_settle_error',\n };\n }\n }\n}\n","/**\n * x402 v2 Server\n *\n * Server-side helpers for accepting x402 payments.\n * Chain-agnostic - works with Solana, Base, and any x402-compatible network.\n *\n * @example\n * ```typescript\n * import { createX402Server } from '@dexterai/x402/server';\n *\n * const server = createX402Server({\n * payTo: 'YourAddress...',\n * network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n * });\n *\n * // Handle 402 responses\n * if (!paymentSignature) {\n * const requirements = await server.buildRequirements({\n * amountAtomic: '50000',\n * resourceUrl: '/api/protected',\n * });\n * res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));\n * res.status(402).json({});\n * return;\n * }\n *\n * // Verify and settle\n * const verify = await server.verifyPayment(paymentSignature);\n * if (!verify.isValid) throw new Error(verify.invalidReason);\n *\n * const settle = await server.settlePayment(paymentSignature);\n * if (!settle.success) throw new Error(settle.errorReason);\n *\n * // Payment successful!\n * res.json({ transaction: settle.transaction });\n * ```\n */\n\nimport type {\n PaymentRequired,\n PaymentAccept,\n ResourceInfo,\n AcceptsExtra,\n VerifyResponse,\n SettleResponse,\n} from '../types';\nimport {\n SOLANA_MAINNET_NETWORK,\n USDC_MINT,\n DEXTER_FACILITATOR_URL,\n} from '../types';\nimport { FacilitatorClient, type SupportedKind } from './facilitator-client';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Asset configuration\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n}\n\n/**\n * Server configuration\n */\nexport interface X402ServerConfig {\n /** Address to receive payments */\n payTo: string;\n /** Facilitator URL (defaults to Dexter) */\n facilitatorUrl?: string;\n /** CAIP-2 network identifier */\n network?: string;\n /** Asset configuration (defaults to USDC) */\n asset?: AssetConfig;\n /** Default payment timeout in seconds */\n defaultTimeoutSeconds?: number;\n}\n\n/**\n * Options for building payment requirements\n */\nexport interface BuildRequirementsOptions {\n /** Amount in atomic units (e.g., '50000' for 0.05 USDC) */\n amountAtomic: string;\n /** Full URL of the resource */\n resourceUrl: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the response */\n mimeType?: string;\n /** Override timeout for this request */\n timeoutSeconds?: number;\n}\n\n/**\n * x402 Server interface\n */\nexport interface X402Server {\n /** Build payment requirements (fetches feePayer from facilitator) */\n buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired>;\n\n /** Encode requirements for PAYMENT-REQUIRED header */\n encodeRequirements(requirements: PaymentRequired): string;\n\n /** Create complete 402 response object */\n create402Response(requirements: PaymentRequired): {\n status: 402;\n headers: { 'PAYMENT-REQUIRED': string };\n body: Record<string, unknown>;\n };\n\n /** Verify payment with facilitator */\n verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse>;\n\n /** Settle payment via facilitator */\n settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse>;\n\n /** Get PaymentAccept for verify/settle */\n getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept>;\n\n /** Get network this server is configured for */\n readonly network: string;\n\n /** Get facilitator client for advanced usage */\n readonly facilitator: FacilitatorClient;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Create an x402 server for accepting payments\n */\nexport function createX402Server(config: X402ServerConfig): X402Server {\n const {\n payTo,\n facilitatorUrl = DEXTER_FACILITATOR_URL,\n network = SOLANA_MAINNET_NETWORK,\n asset = { address: USDC_MINT, decimals: 6 },\n defaultTimeoutSeconds = 60,\n } = config;\n\n const facilitator = new FacilitatorClient(facilitatorUrl);\n\n // Cache for network extra data\n let cachedExtra: SupportedKind['extra'] | null = null;\n\n /**\n * Get extra data from facilitator (cached)\n */\n async function getNetworkExtra(): Promise<AcceptsExtra> {\n if (!cachedExtra) {\n cachedExtra = await facilitator.getNetworkExtra(network);\n }\n\n if (!cachedExtra?.feePayer) {\n throw new Error(`Facilitator does not provide feePayer for network \"${network}\"`);\n }\n\n return {\n feePayer: cachedExtra.feePayer,\n decimals: cachedExtra.decimals ?? asset.decimals,\n // Include any additional EIP-712 data for EVM chains\n name: cachedExtra.name,\n version: cachedExtra.version,\n };\n }\n\n /**\n * Build a PaymentAccept structure\n */\n async function getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept> {\n const {\n amountAtomic,\n timeoutSeconds = defaultTimeoutSeconds,\n } = options;\n\n const extra = await getNetworkExtra();\n\n return {\n scheme: 'exact',\n network,\n maxAmountRequired: amountAtomic,\n asset: asset.address,\n payTo,\n maxTimeoutSeconds: timeoutSeconds,\n extra,\n };\n }\n\n /**\n * Build payment requirements for a 402 response\n */\n async function buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired> {\n const {\n resourceUrl,\n description,\n mimeType = 'application/json',\n } = options;\n\n const resource: ResourceInfo = {\n url: resourceUrl,\n description,\n mimeType,\n };\n\n const accept = await getPaymentAccept(options);\n\n return {\n x402Version: 2,\n resource,\n accepts: [accept],\n error: 'Payment required',\n };\n }\n\n /**\n * Encode requirements for PAYMENT-REQUIRED header\n */\n function encodeRequirements(requirements: PaymentRequired): string {\n return btoa(JSON.stringify(requirements));\n }\n\n /**\n * Create complete 402 response object\n */\n function create402Response(requirements: PaymentRequired) {\n return {\n status: 402 as const,\n headers: {\n 'PAYMENT-REQUIRED': encodeRequirements(requirements),\n },\n body: {},\n };\n }\n\n /**\n * Verify payment with facilitator\n */\n async function verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse> {\n // If requirements not provided, build default\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.verifyPayment(paymentSignatureHeader, req);\n }\n\n /**\n * Settle payment via facilitator\n */\n async function settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse> {\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.settlePayment(paymentSignatureHeader, req);\n }\n\n return {\n buildRequirements,\n encodeRequirements,\n create402Response,\n verifyPayment,\n settlePayment,\n getPaymentAccept,\n network,\n facilitator,\n };\n}\n","/**\n * Dynamic Pricing for x402\n *\n * Calculate prices based on input length (characters, tokens, etc.)\n * Perfect for LLM/AI endpoints where cost scales with input size.\n *\n * @example\n * ```typescript\n * import { createDynamicPricing } from '@dexterai/x402/server';\n *\n * const pricing = createDynamicPricing({\n * unitSize: 1000, // chars per billing unit\n * ratePerUnit: 0.01, // $0.01 per unit\n * minUsd: 0.01, // floor\n * maxUsd: 10.00, // ceiling (optional)\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '23000', usdAmount: 0.023, quoteHash: 'abc...', units: 2.3 }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for dynamic pricing\n */\nexport interface DynamicPricingConfig {\n /**\n * Characters per billing unit.\n * Example: 1000 means every 1000 chars = 1 unit\n */\n unitSize: number;\n\n /**\n * USD per unit.\n * Example: 0.01 means $0.01 per unit\n */\n ratePerUnit: number;\n\n /**\n * Minimum USD amount (floor).\n * Recommended: 0.01 (practical minimum for settlement)\n * @default 0.01\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * Optional - prevents unexpectedly large bills.\n */\n maxUsd?: number;\n\n /**\n * Rounding mode for unit calculation.\n * - 'ceil': Always round up (fair to seller)\n * - 'floor': Always round down (fair to buyer)\n * - 'round': Standard rounding\n * @default 'ceil'\n */\n roundingMode?: 'ceil' | 'floor' | 'round';\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Price quote returned by calculate()\n */\nexport interface PriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount (for display) */\n usdAmount: number;\n\n /**\n * Quote hash for validation.\n * Includes input + config, so config changes invalidate quotes.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n\n /** Number of billing units */\n units: number;\n\n /** Input length in characters */\n inputLength: number;\n}\n\n/**\n * Dynamic pricing calculator\n */\nexport interface DynamicPricing {\n /** Calculate price from input */\n calculate(input: string): PriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Get pricing config (for display) */\n readonly config: Required<DynamicPricingConfig>;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Simple hash function (browser-compatible, no crypto needed)\n * Uses FNV-1a for speed and simplicity\n */\nfunction simpleHash(str: string): string {\n let hash = 2166136261; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 16777619); // FNV prime\n }\n // Convert to hex string\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Create a dynamic pricing calculator\n */\nexport function createDynamicPricing(config: DynamicPricingConfig): DynamicPricing {\n const fullConfig: Required<DynamicPricingConfig> = {\n unitSize: config.unitSize,\n ratePerUnit: config.ratePerUnit,\n minUsd: config.minUsd ?? 0.01,\n maxUsd: config.maxUsd ?? Infinity,\n roundingMode: config.roundingMode ?? 'ceil',\n decimals: config.decimals ?? 6,\n };\n\n const { unitSize, ratePerUnit, minUsd, maxUsd, roundingMode, decimals } = fullConfig;\n\n // Validate config\n if (unitSize <= 0) throw new Error('unitSize must be positive');\n if (ratePerUnit <= 0) throw new Error('ratePerUnit must be positive');\n if (minUsd < 0) throw new Error('minUsd cannot be negative');\n if (maxUsd < minUsd) throw new Error('maxUsd must be >= minUsd');\n\n /**\n * Build hash input string (input + config)\n * Config is included so pricing changes invalidate old quotes\n */\n function buildHashInput(input: string): string {\n const configStr = JSON.stringify({\n unitSize,\n ratePerUnit,\n minUsd,\n maxUsd: maxUsd === Infinity ? 'none' : maxUsd,\n roundingMode,\n });\n return `${input}|${configStr}`;\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string): PriceQuote {\n const inputLength = input.length;\n\n // Calculate units based on rounding mode\n const rawUnits = inputLength / unitSize;\n let units: number;\n switch (roundingMode) {\n case 'ceil':\n units = Math.ceil(rawUnits);\n break;\n case 'floor':\n units = Math.floor(rawUnits);\n break;\n case 'round':\n units = Math.round(rawUnits);\n break;\n }\n\n // Ensure at least 1 unit if there's any input\n if (inputLength > 0 && units === 0) {\n units = 1;\n }\n\n // Calculate USD amount\n let usdAmount = units * ratePerUnit;\n\n // Apply min/max\n usdAmount = Math.max(minUsd, usdAmount);\n usdAmount = Math.min(maxUsd, usdAmount);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash (includes config)\n const quoteHash = simpleHash(buildHashInput(input));\n\n return {\n amountAtomic,\n usdAmount,\n quoteHash,\n units,\n inputLength,\n };\n }\n\n /**\n * Validate quote hash\n * Returns true if the hash matches (input + config unchanged)\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const expectedHash = simpleHash(buildHashInput(input));\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n config: fullConfig,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Format pricing for display\n * Example: \"from $0.01 per 1,000 chars\"\n */\nexport function formatPricing(config: DynamicPricingConfig): string {\n const rate = config.ratePerUnit.toFixed(2);\n const units = config.unitSize.toLocaleString();\n return `from $${rate} per ${units} chars`;\n}\n\n\n\n","/**\n * Token-Based Pricing for x402\n *\n * Accurate LLM pricing using tiktoken for token counting.\n * Uses real OpenAI model rates for precise cost calculation.\n *\n * @example\n * ```typescript\n * import { createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';\n *\n * const pricing = createTokenPricing({\n * model: 'gpt-4o-mini',\n * // Optional overrides:\n * // minUsd: 0.001,\n * // maxUsd: 50.0,\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '1500', usdAmount: 0.0015, inputTokens: 100, quoteHash: 'abc...' }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\nimport { createHash } from 'crypto';\nimport { encoding_for_model, get_encoding, type TiktokenModel } from 'tiktoken';\n\n// ============================================================================\n// Model Pricing Table\n// ============================================================================\n\n/**\n * Pricing info for a model\n */\nexport interface ModelPricing {\n /** USD per 1M input tokens */\n input: number;\n /** USD per 1M output tokens */\n output: number;\n /** USD per 1M cached input tokens (optional) */\n cached?: number;\n /** Default max output tokens for this model */\n maxTokens: number;\n /** Pricing tier */\n tier: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';\n}\n\n/**\n * OpenAI Model Pricing - USD per million tokens\n * Updated: December 2024\n */\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // === FAST TIER (cheapest, fastest) ===\n 'gpt-4o-mini': { \n input: 0.15, output: 0.6, cached: 0.075, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-mini': { \n input: 0.4, output: 1.6, cached: 0.1, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-nano': { \n input: 0.1, output: 0.4, cached: 0.025, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-nano': { \n input: 0.05, output: 0.4, cached: 0.005, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-mini': { \n input: 0.25, output: 2.0, cached: 0.025, \n maxTokens: 8192, tier: 'fast' \n },\n\n // === STANDARD TIER (balanced) ===\n 'gpt-4o': { \n input: 2.5, output: 10.0, cached: 1.25, \n maxTokens: 4096, tier: 'standard' \n },\n 'gpt-4.1': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 8192, tier: 'standard' \n },\n 'gpt-5': { \n input: 1.25, output: 10.0, cached: 0.125, \n maxTokens: 8192, tier: 'standard' \n },\n\n // === REASONING TIER (o-series) ===\n 'o1-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o4-mini': { \n input: 1.1, output: 4.4, cached: 0.275, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n 'o1': { \n input: 15.0, output: 60.0, cached: 7.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n\n // === PREMIUM TIER (expensive, specialized) ===\n 'o3-pro': { \n input: 20.0, output: 80.0, \n maxTokens: 32768, tier: 'premium' \n },\n 'o1-pro': { \n input: 150.0, output: 600.0, \n maxTokens: 32768, tier: 'premium' \n },\n};\n\n// Default model for fallback\nconst DEFAULT_MODEL = 'gpt-4o-mini';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for token-based pricing\n */\nexport interface TokenPricingConfig {\n /**\n * Model name. Used for display and to look up built-in pricing.\n * If not in MODEL_PRICING and no custom rates provided, falls back to gpt-4o-mini rates.\n */\n model?: string;\n\n /**\n * Custom input rate (USD per 1M tokens).\n * Overrides built-in MODEL_PRICING for this model.\n * Use this for Anthropic, Gemini, Mistral, or any custom model.\n */\n inputRate?: number;\n\n /**\n * Custom output rate (USD per 1M tokens).\n * Optional - used for display/info purposes.\n */\n outputRate?: number;\n\n /**\n * Custom max output tokens.\n * @default 4096\n */\n maxTokens?: number;\n\n /**\n * Custom tier label.\n * @default 'custom'\n */\n tier?: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';\n\n /**\n * Custom tokenizer function.\n * If not provided, uses tiktoken (cl100k_base encoding).\n * Use this for models with different tokenization (e.g., Llama, Mistral).\n * \n * @example\n * tokenizer: (text) => llamaTokenizer.encode(text).length\n */\n tokenizer?: (text: string) => number;\n\n /**\n * Minimum USD amount (floor).\n * @default 0.001\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * @default 50.0\n */\n maxUsd?: number;\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Token price quote\n */\nexport interface TokenPriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount */\n usdAmount: number;\n\n /** Number of input tokens */\n inputTokens: number;\n\n /** Model used for pricing */\n model: string;\n\n /** Pricing tier */\n tier: string;\n\n /** Input rate per million tokens */\n inputRatePerMillion: number;\n\n /** Output rate per million tokens */\n outputRatePerMillion: number;\n\n /** Max output tokens for this model */\n maxOutputTokens: number;\n\n /**\n * Quote hash for validation.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n}\n\n/**\n * Token pricing calculator\n */\nexport interface TokenPricing {\n /** Calculate price from input text */\n calculate(input: string, systemPrompt?: string): TokenPriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Count tokens in a string */\n countTokens(input: string): number;\n\n /** Get pricing config */\n readonly config: Required<TokenPricingConfig>;\n\n /** Get model info */\n readonly modelInfo: ModelPricing;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Get tiktoken encoding for a model.\n * Falls back to cl100k_base for unknown models.\n */\nfunction getEncodingForModel(model: string) {\n try {\n return encoding_for_model(model as TiktokenModel);\n } catch {\n // Fall back to cl100k_base (GPT-4/4o family encoding)\n return get_encoding('cl100k_base');\n }\n}\n\n/**\n * Count tokens in a string using tiktoken.\n */\nexport function countTokens(text: string, model: string = DEFAULT_MODEL): number {\n const encoding = getEncodingForModel(model);\n try {\n const tokens = encoding.encode(text);\n return tokens.length;\n } finally {\n encoding.free();\n }\n}\n\n/**\n * Generate a hash of the prompt + pricing config for validation.\n */\nfunction generateQuoteHash(prompt: string, model: string, rate: number, tokens: number): string {\n const configString = JSON.stringify({ model, rate, tokens });\n return createHash('sha256').update(prompt + configString).digest('hex').slice(0, 16);\n}\n\n/**\n * Create a token-based pricing calculator\n */\nexport function createTokenPricing(config: TokenPricingConfig = {}): TokenPricing {\n // Determine model name\n const model = config.model ?? DEFAULT_MODEL;\n \n // Get built-in pricing if available, otherwise use custom or default\n const builtInPricing = MODEL_PRICING[model];\n \n // Build effective model info (custom rates override built-in)\n const modelInfo: ModelPricing = {\n input: config.inputRate ?? builtInPricing?.input ?? MODEL_PRICING[DEFAULT_MODEL].input,\n output: config.outputRate ?? builtInPricing?.output ?? MODEL_PRICING[DEFAULT_MODEL].output,\n maxTokens: config.maxTokens ?? builtInPricing?.maxTokens ?? 4096,\n tier: config.tier ?? builtInPricing?.tier ?? 'custom',\n };\n\n // Custom tokenizer or default tiktoken\n const customTokenizer = config.tokenizer;\n\n const fullConfig: Required<TokenPricingConfig> = {\n model,\n inputRate: modelInfo.input,\n outputRate: modelInfo.output,\n maxTokens: modelInfo.maxTokens,\n tier: modelInfo.tier,\n tokenizer: customTokenizer ?? ((text: string) => countTokens(text, model)),\n minUsd: config.minUsd ?? 0.001,\n maxUsd: config.maxUsd ?? 50.0,\n decimals: config.decimals ?? 6,\n };\n\n const { minUsd, maxUsd, decimals } = fullConfig;\n\n /**\n * Count tokens in text (uses custom tokenizer if provided)\n */\n function countTokensInternal(input: string): number {\n if (customTokenizer) {\n return customTokenizer(input);\n }\n return countTokens(input, model);\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string, systemPrompt?: string): TokenPriceQuote {\n // Count input tokens (prompt + system prompt if provided)\n const fullInput = systemPrompt ? `${systemPrompt}\\n\\n${input}` : input;\n const inputTokens = countTokensInternal(fullInput);\n\n // Calculate USD cost based on input tokens only\n // Price = (inputTokens / 1,000,000) × inputRate\n let usdAmount = (inputTokens / 1_000_000) * modelInfo.input;\n\n // Apply min/max caps\n usdAmount = Math.max(usdAmount, minUsd);\n usdAmount = Math.min(usdAmount, maxUsd);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash for validation\n const quoteHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n\n return {\n amountAtomic,\n usdAmount,\n inputTokens,\n model,\n tier: modelInfo.tier,\n inputRatePerMillion: modelInfo.input,\n outputRatePerMillion: modelInfo.output,\n maxOutputTokens: modelInfo.maxTokens,\n quoteHash,\n };\n }\n\n /**\n * Validate quote hash\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const inputTokens = countTokensInternal(input);\n const expectedHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n countTokens: countTokensInternal,\n config: fullConfig,\n modelInfo,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Get list of available models with their pricing.\n */\nexport function getAvailableModels(): Array<{\n model: string;\n inputRate: number;\n outputRate: number;\n maxTokens: number;\n tier: string;\n}> {\n return Object.entries(MODEL_PRICING)\n .map(([model, pricing]) => ({\n model,\n inputRate: pricing.input,\n outputRate: pricing.output,\n maxTokens: pricing.maxTokens,\n tier: pricing.tier,\n }))\n .sort((a, b) => {\n // Sort by tier, then by input rate\n const tierOrder = { fast: 0, standard: 1, reasoning: 2, premium: 3 };\n const tierDiff = tierOrder[a.tier as keyof typeof tierOrder] - tierOrder[b.tier as keyof typeof tierOrder];\n if (tierDiff !== 0) return tierDiff;\n return a.inputRate - b.inputRate;\n });\n}\n\n/**\n * Check if a model exists in our pricing.\n */\nexport function isValidModel(model: string): boolean {\n return model in MODEL_PRICING;\n}\n\n/**\n * Format token pricing for display\n * Example: \"$0.15 per 1M tokens (gpt-4o-mini)\"\n */\nexport function formatTokenPricing(model: string = DEFAULT_MODEL): string {\n const pricing = MODEL_PRICING[model] ?? MODEL_PRICING[DEFAULT_MODEL];\n const actualModel = MODEL_PRICING[model] ? model : DEFAULT_MODEL;\n return `$${pricing.input.toFixed(2)} per 1M tokens (${actualModel})`;\n}\n\n"],"mappings":"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","names":[]}
|
package/dist/server/index.d.cts
CHANGED
|
@@ -330,7 +330,7 @@ interface ModelPricing {
|
|
|
330
330
|
/** Default max output tokens for this model */
|
|
331
331
|
maxTokens: number;
|
|
332
332
|
/** Pricing tier */
|
|
333
|
-
tier: 'fast' | 'standard' | 'reasoning' | 'premium';
|
|
333
|
+
tier: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';
|
|
334
334
|
}
|
|
335
335
|
/**
|
|
336
336
|
* OpenAI Model Pricing - USD per million tokens
|
|
@@ -342,10 +342,40 @@ declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
|
342
342
|
*/
|
|
343
343
|
interface TokenPricingConfig {
|
|
344
344
|
/**
|
|
345
|
-
* Model
|
|
346
|
-
*
|
|
345
|
+
* Model name. Used for display and to look up built-in pricing.
|
|
346
|
+
* If not in MODEL_PRICING and no custom rates provided, falls back to gpt-4o-mini rates.
|
|
347
347
|
*/
|
|
348
348
|
model?: string;
|
|
349
|
+
/**
|
|
350
|
+
* Custom input rate (USD per 1M tokens).
|
|
351
|
+
* Overrides built-in MODEL_PRICING for this model.
|
|
352
|
+
* Use this for Anthropic, Gemini, Mistral, or any custom model.
|
|
353
|
+
*/
|
|
354
|
+
inputRate?: number;
|
|
355
|
+
/**
|
|
356
|
+
* Custom output rate (USD per 1M tokens).
|
|
357
|
+
* Optional - used for display/info purposes.
|
|
358
|
+
*/
|
|
359
|
+
outputRate?: number;
|
|
360
|
+
/**
|
|
361
|
+
* Custom max output tokens.
|
|
362
|
+
* @default 4096
|
|
363
|
+
*/
|
|
364
|
+
maxTokens?: number;
|
|
365
|
+
/**
|
|
366
|
+
* Custom tier label.
|
|
367
|
+
* @default 'custom'
|
|
368
|
+
*/
|
|
369
|
+
tier?: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';
|
|
370
|
+
/**
|
|
371
|
+
* Custom tokenizer function.
|
|
372
|
+
* If not provided, uses tiktoken (cl100k_base encoding).
|
|
373
|
+
* Use this for models with different tokenization (e.g., Llama, Mistral).
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* tokenizer: (text) => llamaTokenizer.encode(text).length
|
|
377
|
+
*/
|
|
378
|
+
tokenizer?: (text: string) => number;
|
|
349
379
|
/**
|
|
350
380
|
* Minimum USD amount (floor).
|
|
351
381
|
* @default 0.001
|
package/dist/server/index.d.ts
CHANGED
|
@@ -330,7 +330,7 @@ interface ModelPricing {
|
|
|
330
330
|
/** Default max output tokens for this model */
|
|
331
331
|
maxTokens: number;
|
|
332
332
|
/** Pricing tier */
|
|
333
|
-
tier: 'fast' | 'standard' | 'reasoning' | 'premium';
|
|
333
|
+
tier: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';
|
|
334
334
|
}
|
|
335
335
|
/**
|
|
336
336
|
* OpenAI Model Pricing - USD per million tokens
|
|
@@ -342,10 +342,40 @@ declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
|
342
342
|
*/
|
|
343
343
|
interface TokenPricingConfig {
|
|
344
344
|
/**
|
|
345
|
-
* Model
|
|
346
|
-
*
|
|
345
|
+
* Model name. Used for display and to look up built-in pricing.
|
|
346
|
+
* If not in MODEL_PRICING and no custom rates provided, falls back to gpt-4o-mini rates.
|
|
347
347
|
*/
|
|
348
348
|
model?: string;
|
|
349
|
+
/**
|
|
350
|
+
* Custom input rate (USD per 1M tokens).
|
|
351
|
+
* Overrides built-in MODEL_PRICING for this model.
|
|
352
|
+
* Use this for Anthropic, Gemini, Mistral, or any custom model.
|
|
353
|
+
*/
|
|
354
|
+
inputRate?: number;
|
|
355
|
+
/**
|
|
356
|
+
* Custom output rate (USD per 1M tokens).
|
|
357
|
+
* Optional - used for display/info purposes.
|
|
358
|
+
*/
|
|
359
|
+
outputRate?: number;
|
|
360
|
+
/**
|
|
361
|
+
* Custom max output tokens.
|
|
362
|
+
* @default 4096
|
|
363
|
+
*/
|
|
364
|
+
maxTokens?: number;
|
|
365
|
+
/**
|
|
366
|
+
* Custom tier label.
|
|
367
|
+
* @default 'custom'
|
|
368
|
+
*/
|
|
369
|
+
tier?: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';
|
|
370
|
+
/**
|
|
371
|
+
* Custom tokenizer function.
|
|
372
|
+
* If not provided, uses tiktoken (cl100k_base encoding).
|
|
373
|
+
* Use this for models with different tokenization (e.g., Llama, Mistral).
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* tokenizer: (text) => llamaTokenizer.encode(text).length
|
|
377
|
+
*/
|
|
378
|
+
tokenizer?: (text: string) => number;
|
|
349
379
|
/**
|
|
350
380
|
* Minimum USD amount (floor).
|
|
351
381
|
* @default 0.001
|
package/dist/server/index.js
CHANGED
|
@@ -467,16 +467,31 @@ function generateQuoteHash(prompt, model, rate, tokens) {
|
|
|
467
467
|
return createHash("sha256").update(prompt + configString).digest("hex").slice(0, 16);
|
|
468
468
|
}
|
|
469
469
|
function createTokenPricing(config = {}) {
|
|
470
|
-
const model = config.model
|
|
471
|
-
const
|
|
470
|
+
const model = config.model ?? DEFAULT_MODEL;
|
|
471
|
+
const builtInPricing = MODEL_PRICING[model];
|
|
472
|
+
const modelInfo = {
|
|
473
|
+
input: config.inputRate ?? builtInPricing?.input ?? MODEL_PRICING[DEFAULT_MODEL].input,
|
|
474
|
+
output: config.outputRate ?? builtInPricing?.output ?? MODEL_PRICING[DEFAULT_MODEL].output,
|
|
475
|
+
maxTokens: config.maxTokens ?? builtInPricing?.maxTokens ?? 4096,
|
|
476
|
+
tier: config.tier ?? builtInPricing?.tier ?? "custom"
|
|
477
|
+
};
|
|
478
|
+
const customTokenizer = config.tokenizer;
|
|
472
479
|
const fullConfig = {
|
|
473
480
|
model,
|
|
481
|
+
inputRate: modelInfo.input,
|
|
482
|
+
outputRate: modelInfo.output,
|
|
483
|
+
maxTokens: modelInfo.maxTokens,
|
|
484
|
+
tier: modelInfo.tier,
|
|
485
|
+
tokenizer: customTokenizer ?? ((text) => countTokens(text, model)),
|
|
474
486
|
minUsd: config.minUsd ?? 1e-3,
|
|
475
487
|
maxUsd: config.maxUsd ?? 50,
|
|
476
488
|
decimals: config.decimals ?? 6
|
|
477
489
|
};
|
|
478
490
|
const { minUsd, maxUsd, decimals } = fullConfig;
|
|
479
491
|
function countTokensInternal(input) {
|
|
492
|
+
if (customTokenizer) {
|
|
493
|
+
return customTokenizer(input);
|
|
494
|
+
}
|
|
480
495
|
return countTokens(input, model);
|
|
481
496
|
}
|
|
482
497
|
function calculate(input, systemPrompt) {
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types.ts","../../src/utils.ts","../../src/server/facilitator-client.ts","../../src/server/x402-server.ts","../../src/server/dynamic-pricing.ts","../../src/server/token-pricing.ts"],"sourcesContent":["/**\n * x402 v2 SDK — Shared Types\n *\n * Chain-agnostic types for x402 v2 payments.\n * Works with Solana, Base, and any future x402-compatible networks.\n */\n\n// ============================================================================\n// Network Constants\n// ============================================================================\n\n/** CAIP-2 network identifier for Solana mainnet */\nexport const SOLANA_MAINNET_NETWORK = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';\n\n/** CAIP-2 network identifier for Base mainnet */\nexport const BASE_MAINNET_NETWORK = 'eip155:8453';\n\n/** Alias for Solana mainnet */\nexport const SOLANA_MAINNET = SOLANA_MAINNET_NETWORK;\n\n/** Alias for Base mainnet */\nexport const BASE_MAINNET = BASE_MAINNET_NETWORK;\n\n// ============================================================================\n// Asset Constants\n// ============================================================================\n\n/** USDC mint on Solana mainnet */\nexport const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\n\n/** USDC address on Base mainnet */\nexport const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';\n\n// ============================================================================\n// Facilitator Constants\n// ============================================================================\n\n/** Dexter's public x402 v2 facilitator URL */\nexport const DEXTER_FACILITATOR_URL = 'https://x402.dexter.cash';\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/**\n * Asset configuration for payments\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n /** Optional: Human-readable symbol */\n symbol?: string;\n}\n\n/**\n * Resource info included in payment requirements\n */\nexport interface ResourceInfo {\n /** Resource URL */\n url: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the resource */\n mimeType?: string;\n}\n\n/**\n * Extra fields in payment requirements\n * Chain-specific fields may vary\n */\nexport interface AcceptsExtra {\n /** Facilitator address that pays tx fees (required for Solana) */\n feePayer?: string;\n /** Token decimals (optional - defaults to 6 for USDC) */\n decimals?: number;\n /** EIP-712: Token name (EVM only) */\n name?: string;\n /** EIP-712: Token version (EVM only) */\n version?: string;\n /** Additional chain-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * A single payment option in the accepts array\n */\nexport interface PaymentAccept {\n /** Payment scheme (always 'exact' for x402 v2) */\n scheme: 'exact';\n /** CAIP-2 network identifier */\n network: string;\n /** Payment amount in atomic units (x402 spec field - REQUIRED) */\n maxAmountRequired: string;\n /** Alias for maxAmountRequired (for convenience) */\n amount?: string;\n /** Token address */\n asset: string;\n /** Seller's address to receive payment */\n payTo: string;\n /** Maximum seconds until payment expires */\n maxTimeoutSeconds: number;\n /** Chain-specific extra data */\n extra: AcceptsExtra;\n}\n\n/**\n * Full PaymentRequired structure (sent in PAYMENT-REQUIRED header)\n */\nexport interface PaymentRequired {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** Available payment options */\n accepts: PaymentAccept[];\n /** Optional error message */\n error?: string;\n}\n\n/**\n * PaymentSignature structure (sent in PAYMENT-SIGNATURE header)\n */\nexport interface PaymentSignature {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** The payment option that was accepted */\n accepted: PaymentAccept;\n /** The signed payment */\n payload: {\n /** Signed transaction (base64 for Solana, JSON for EVM) */\n transaction: string;\n };\n}\n\n// ============================================================================\n// Facilitator Response Types\n// ============================================================================\n\n/**\n * Response from /verify endpoint\n */\nexport interface VerifyResponse {\n /** Whether the payment is valid */\n isValid: boolean;\n /** Reason for invalidity (if invalid) */\n invalidReason?: string;\n /** Payer address */\n payer?: string;\n}\n\n/**\n * Response from /settle endpoint\n */\nexport interface SettleResponse {\n /** Whether settlement succeeded */\n success: boolean;\n /** Transaction signature/hash */\n transaction?: string;\n /** Network the payment was made on */\n network: string;\n /** Error reason (if failed) */\n errorReason?: string;\n /** Error code (if failed) */\n errorCode?: string;\n /** Payer address */\n payer?: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * SDK error codes\n */\nexport type X402ErrorCode =\n // Client errors\n | 'missing_payment_required_header'\n | 'invalid_payment_required'\n | 'unsupported_network'\n | 'no_matching_payment_option'\n | 'no_solana_accept' // Legacy, kept for compatibility\n | 'missing_fee_payer'\n | 'missing_decimals'\n | 'missing_amount'\n | 'amount_exceeds_max'\n | 'insufficient_balance'\n | 'wallet_missing_sign_transaction'\n | 'wallet_not_connected'\n | 'transaction_build_failed'\n | 'payment_rejected'\n // Server errors\n | 'invalid_payment_signature'\n | 'facilitator_verify_failed'\n | 'facilitator_settle_failed'\n | 'facilitator_request_failed'\n | 'no_matching_requirement';\n\n/**\n * Custom error class for x402 operations\n */\nexport class X402Error extends Error {\n /** Error code for programmatic handling */\n code: X402ErrorCode;\n /** Additional error details */\n details?: unknown;\n\n constructor(code: X402ErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'X402Error';\n this.code = code;\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, X402Error.prototype);\n }\n}\n","/**\n * Utility Functions\n *\n * Chain-agnostic helpers for x402 payments.\n */\n\nimport { SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK } from './types';\n\n// ============================================================================\n// Amount Conversion\n// ============================================================================\n\n/**\n * Convert human-readable amount to atomic units\n *\n * @param amount - Human-readable amount (e.g., 0.05 for $0.05)\n * @param decimals - Token decimals (e.g., 6 for USDC)\n * @returns Amount in atomic units as string\n *\n * @example\n * ```typescript\n * toAtomicUnits(0.05, 6) // '50000'\n * toAtomicUnits(1.50, 6) // '1500000'\n * ```\n */\nexport function toAtomicUnits(amount: number, decimals: number): string {\n const multiplier = Math.pow(10, decimals);\n return Math.floor(amount * multiplier).toString();\n}\n\n/**\n * Convert atomic units to human-readable amount\n *\n * @param atomicUnits - Amount in smallest units\n * @param decimals - Token decimals\n * @returns Human-readable amount\n *\n * @example\n * ```typescript\n * fromAtomicUnits('50000', 6) // 0.05\n * fromAtomicUnits(1500000n, 6) // 1.5\n * ```\n */\nexport function fromAtomicUnits(\n atomicUnits: string | bigint | number,\n decimals: number\n): number {\n const divisor = Math.pow(10, decimals);\n return Number(atomicUnits) / divisor;\n}\n\n// ============================================================================\n// Network Helpers\n// ============================================================================\n\n/**\n * Network type\n */\nexport type ChainFamily = 'solana' | 'evm' | 'unknown';\n\n/**\n * Get the chain family from a CAIP-2 network identifier\n *\n * @param network - CAIP-2 network identifier\n * @returns Chain family\n *\n * @example\n * ```typescript\n * getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'\n * getChainFamily('eip155:8453') // 'evm'\n * ```\n */\nexport function getChainFamily(network: string): ChainFamily {\n if (network.startsWith('solana:') || network === 'solana') {\n return 'solana';\n }\n if (network.startsWith('eip155:') || ['base', 'ethereum', 'arbitrum'].includes(network)) {\n return 'evm';\n }\n return 'unknown';\n}\n\n/**\n * Get default RPC URL for a network\n *\n * @param network - CAIP-2 network identifier\n * @returns Default RPC URL\n */\nexport function getDefaultRpcUrl(network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n if (network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1') {\n return 'https://api.devnet.solana.com';\n }\n if (network.includes('testnet') || network === 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z') {\n return 'https://api.testnet.solana.com';\n }\n // Mainnet uses Dexter's RPC proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n }\n\n if (family === 'evm') {\n // Extract chain ID from CAIP-2\n if (network.startsWith('eip155:')) {\n const chainId = network.split(':')[1];\n switch (chainId) {\n case '8453': return 'https://api.dexter.cash/api/base/rpc'; // Dexter proxy\n case '84532': return 'https://sepolia.base.org';\n case '1': return 'https://eth.llamarpc.com';\n case '42161': return 'https://arb1.arbitrum.io/rpc';\n default: return 'https://api.dexter.cash/api/base/rpc';\n }\n }\n // Legacy names\n if (network === 'base') return 'https://api.dexter.cash/api/base/rpc';\n if (network === 'ethereum') return 'https://eth.llamarpc.com';\n if (network === 'arbitrum') return 'https://arb1.arbitrum.io/rpc';\n return 'https://api.dexter.cash/api/base/rpc';\n }\n\n // Unknown - return Dexter's Solana proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n}\n\n/**\n * Get human-readable chain name\n *\n * @param network - CAIP-2 network identifier\n * @returns Human-readable name\n */\nexport function getChainName(network: string): string {\n const mapping: Record<string, string> = {\n [SOLANA_MAINNET_NETWORK]: 'Solana',\n 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'Solana Devnet',\n 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'Solana Testnet',\n 'solana': 'Solana',\n [BASE_MAINNET_NETWORK]: 'Base',\n 'eip155:84532': 'Base Sepolia',\n 'eip155:1': 'Ethereum',\n 'eip155:42161': 'Arbitrum One',\n 'base': 'Base',\n 'ethereum': 'Ethereum',\n 'arbitrum': 'Arbitrum',\n };\n return mapping[network] || network;\n}\n\n// ============================================================================\n// Transaction URL Helpers\n// ============================================================================\n\n/**\n * Get explorer URL for a transaction\n *\n * @param txSignature - Transaction signature/hash\n * @param network - CAIP-2 network identifier\n * @returns Explorer URL\n */\nexport function getExplorerUrl(txSignature: string, network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n const isDevnet = network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';\n if (isDevnet) {\n return `https://solscan.io/tx/${txSignature}?cluster=devnet`;\n }\n // Prefer Orb Markets for mainnet\n return `https://www.orbmarkets.io/tx/${txSignature}`;\n }\n\n if (family === 'evm') {\n // Extract chain ID\n let chainId = '8453'; // Default to Base\n if (network.startsWith('eip155:')) {\n chainId = network.split(':')[1];\n } else if (network === 'ethereum') {\n chainId = '1';\n } else if (network === 'arbitrum') {\n chainId = '42161';\n }\n\n switch (chainId) {\n case '8453': return `https://basescan.org/tx/${txSignature}`;\n case '84532': return `https://sepolia.basescan.org/tx/${txSignature}`;\n case '1': return `https://etherscan.io/tx/${txSignature}`;\n case '42161': return `https://arbiscan.io/tx/${txSignature}`;\n default: return `https://basescan.org/tx/${txSignature}`;\n }\n }\n\n return `https://solscan.io/tx/${txSignature}`;\n}\n\n// ============================================================================\n// Encoding Helpers\n// ============================================================================\n\n/**\n * Encode an object as base64 JSON\n */\nexport function encodeBase64Json(obj: unknown): string {\n return btoa(JSON.stringify(obj));\n}\n\n/**\n * Decode base64 JSON to object\n */\nexport function decodeBase64Json<T>(encoded: string): T {\n return JSON.parse(atob(encoded)) as T;\n}\n","/**\n * Facilitator Client\n *\n * Communicates with the x402 facilitator for:\n * - /supported - Get supported payment schemes and fee payer addresses\n * - /verify - Verify a payment signature before processing\n * - /settle - Submit the payment for execution\n *\n * Works with any x402 v2 facilitator (Dexter or others).\n */\n\nimport type { PaymentAccept, PaymentSignature, VerifyResponse, SettleResponse } from '../types';\nimport { DEXTER_FACILITATOR_URL } from '../types';\nimport { decodeBase64Json } from '../utils';\n\n/**\n * Supported payment kind from facilitator /supported endpoint\n */\nexport interface SupportedKind {\n x402Version: number;\n scheme: string;\n network: string;\n extra?: {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Response from facilitator /supported endpoint\n */\nexport interface SupportedResponse {\n kinds: SupportedKind[];\n}\n\n/**\n * Client for communicating with an x402 v2 facilitator\n */\nexport class FacilitatorClient {\n private facilitatorUrl: string;\n private cachedSupported: SupportedResponse | null = null;\n private cacheTime: number = 0;\n private readonly CACHE_TTL_MS = 60_000; // 1 minute cache\n\n constructor(facilitatorUrl: string = DEXTER_FACILITATOR_URL) {\n this.facilitatorUrl = facilitatorUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Get supported payment kinds from the facilitator\n * Results are cached for 1 minute to reduce network calls\n */\n async getSupported(): Promise<SupportedResponse> {\n const now = Date.now();\n if (this.cachedSupported && now - this.cacheTime < this.CACHE_TTL_MS) {\n return this.cachedSupported;\n }\n\n const response = await fetch(`${this.facilitatorUrl}/supported`);\n if (!response.ok) {\n throw new Error(`Facilitator /supported returned ${response.status}`);\n }\n\n this.cachedSupported = (await response.json()) as SupportedResponse;\n this.cacheTime = now;\n return this.cachedSupported;\n }\n\n /**\n * Get the fee payer address for a specific network\n *\n * @param network - CAIP-2 network identifier\n * @returns Fee payer address\n */\n async getFeePayer(network: string): Promise<string> {\n const supported = await this.getSupported();\n\n // Find matching network support (exact match or v2)\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n if (!kind?.extra?.feePayer) {\n throw new Error(\n `Facilitator does not support network \"${network}\" with scheme \"exact\", or feePayer not provided`\n );\n }\n\n return kind.extra.feePayer;\n }\n\n /**\n * Get extra data for a network (feePayer, decimals, EIP-712 data, etc.)\n *\n * @param network - CAIP-2 network identifier\n * @returns Extra data from /supported\n */\n async getNetworkExtra(network: string): Promise<SupportedKind['extra']> {\n const supported = await this.getSupported();\n\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n return kind?.extra;\n }\n\n /**\n * Verify a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Verification response\n */\n async verifyPayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<VerifyResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const verifyPayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(verifyPayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /verify returned ${response.status}:`, errorText);\n return {\n isValid: false,\n invalidReason: `facilitator_error_${response.status}`,\n };\n }\n\n return (await response.json()) as VerifyResponse;\n } catch (error) {\n console.error('Payment verification failed:', error);\n return {\n isValid: false,\n invalidReason: error instanceof Error ? error.message : 'unexpected_verify_error',\n };\n }\n }\n\n /**\n * Settle a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Settlement response with transaction signature on success\n */\n async settlePayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<SettleResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const settlePayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/settle`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(settlePayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /settle returned ${response.status}:`, errorText);\n return {\n success: false,\n network: requirements.network,\n errorReason: `facilitator_error_${response.status}`,\n };\n }\n\n const result = (await response.json()) as SettleResponse;\n return {\n ...result,\n network: requirements.network,\n };\n } catch (error) {\n console.error('Payment settlement failed:', error);\n return {\n success: false,\n network: requirements.network,\n errorReason: error instanceof Error ? error.message : 'unexpected_settle_error',\n };\n }\n }\n}\n","/**\n * x402 v2 Server\n *\n * Server-side helpers for accepting x402 payments.\n * Chain-agnostic - works with Solana, Base, and any x402-compatible network.\n *\n * @example\n * ```typescript\n * import { createX402Server } from '@dexterai/x402/server';\n *\n * const server = createX402Server({\n * payTo: 'YourAddress...',\n * network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n * });\n *\n * // Handle 402 responses\n * if (!paymentSignature) {\n * const requirements = await server.buildRequirements({\n * amountAtomic: '50000',\n * resourceUrl: '/api/protected',\n * });\n * res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));\n * res.status(402).json({});\n * return;\n * }\n *\n * // Verify and settle\n * const verify = await server.verifyPayment(paymentSignature);\n * if (!verify.isValid) throw new Error(verify.invalidReason);\n *\n * const settle = await server.settlePayment(paymentSignature);\n * if (!settle.success) throw new Error(settle.errorReason);\n *\n * // Payment successful!\n * res.json({ transaction: settle.transaction });\n * ```\n */\n\nimport type {\n PaymentRequired,\n PaymentAccept,\n ResourceInfo,\n AcceptsExtra,\n VerifyResponse,\n SettleResponse,\n} from '../types';\nimport {\n SOLANA_MAINNET_NETWORK,\n USDC_MINT,\n DEXTER_FACILITATOR_URL,\n} from '../types';\nimport { FacilitatorClient, type SupportedKind } from './facilitator-client';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Asset configuration\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n}\n\n/**\n * Server configuration\n */\nexport interface X402ServerConfig {\n /** Address to receive payments */\n payTo: string;\n /** Facilitator URL (defaults to Dexter) */\n facilitatorUrl?: string;\n /** CAIP-2 network identifier */\n network?: string;\n /** Asset configuration (defaults to USDC) */\n asset?: AssetConfig;\n /** Default payment timeout in seconds */\n defaultTimeoutSeconds?: number;\n}\n\n/**\n * Options for building payment requirements\n */\nexport interface BuildRequirementsOptions {\n /** Amount in atomic units (e.g., '50000' for 0.05 USDC) */\n amountAtomic: string;\n /** Full URL of the resource */\n resourceUrl: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the response */\n mimeType?: string;\n /** Override timeout for this request */\n timeoutSeconds?: number;\n}\n\n/**\n * x402 Server interface\n */\nexport interface X402Server {\n /** Build payment requirements (fetches feePayer from facilitator) */\n buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired>;\n\n /** Encode requirements for PAYMENT-REQUIRED header */\n encodeRequirements(requirements: PaymentRequired): string;\n\n /** Create complete 402 response object */\n create402Response(requirements: PaymentRequired): {\n status: 402;\n headers: { 'PAYMENT-REQUIRED': string };\n body: Record<string, unknown>;\n };\n\n /** Verify payment with facilitator */\n verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse>;\n\n /** Settle payment via facilitator */\n settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse>;\n\n /** Get PaymentAccept for verify/settle */\n getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept>;\n\n /** Get network this server is configured for */\n readonly network: string;\n\n /** Get facilitator client for advanced usage */\n readonly facilitator: FacilitatorClient;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Create an x402 server for accepting payments\n */\nexport function createX402Server(config: X402ServerConfig): X402Server {\n const {\n payTo,\n facilitatorUrl = DEXTER_FACILITATOR_URL,\n network = SOLANA_MAINNET_NETWORK,\n asset = { address: USDC_MINT, decimals: 6 },\n defaultTimeoutSeconds = 60,\n } = config;\n\n const facilitator = new FacilitatorClient(facilitatorUrl);\n\n // Cache for network extra data\n let cachedExtra: SupportedKind['extra'] | null = null;\n\n /**\n * Get extra data from facilitator (cached)\n */\n async function getNetworkExtra(): Promise<AcceptsExtra> {\n if (!cachedExtra) {\n cachedExtra = await facilitator.getNetworkExtra(network);\n }\n\n if (!cachedExtra?.feePayer) {\n throw new Error(`Facilitator does not provide feePayer for network \"${network}\"`);\n }\n\n return {\n feePayer: cachedExtra.feePayer,\n decimals: cachedExtra.decimals ?? asset.decimals,\n // Include any additional EIP-712 data for EVM chains\n name: cachedExtra.name,\n version: cachedExtra.version,\n };\n }\n\n /**\n * Build a PaymentAccept structure\n */\n async function getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept> {\n const {\n amountAtomic,\n timeoutSeconds = defaultTimeoutSeconds,\n } = options;\n\n const extra = await getNetworkExtra();\n\n return {\n scheme: 'exact',\n network,\n maxAmountRequired: amountAtomic,\n asset: asset.address,\n payTo,\n maxTimeoutSeconds: timeoutSeconds,\n extra,\n };\n }\n\n /**\n * Build payment requirements for a 402 response\n */\n async function buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired> {\n const {\n resourceUrl,\n description,\n mimeType = 'application/json',\n } = options;\n\n const resource: ResourceInfo = {\n url: resourceUrl,\n description,\n mimeType,\n };\n\n const accept = await getPaymentAccept(options);\n\n return {\n x402Version: 2,\n resource,\n accepts: [accept],\n error: 'Payment required',\n };\n }\n\n /**\n * Encode requirements for PAYMENT-REQUIRED header\n */\n function encodeRequirements(requirements: PaymentRequired): string {\n return btoa(JSON.stringify(requirements));\n }\n\n /**\n * Create complete 402 response object\n */\n function create402Response(requirements: PaymentRequired) {\n return {\n status: 402 as const,\n headers: {\n 'PAYMENT-REQUIRED': encodeRequirements(requirements),\n },\n body: {},\n };\n }\n\n /**\n * Verify payment with facilitator\n */\n async function verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse> {\n // If requirements not provided, build default\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.verifyPayment(paymentSignatureHeader, req);\n }\n\n /**\n * Settle payment via facilitator\n */\n async function settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse> {\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.settlePayment(paymentSignatureHeader, req);\n }\n\n return {\n buildRequirements,\n encodeRequirements,\n create402Response,\n verifyPayment,\n settlePayment,\n getPaymentAccept,\n network,\n facilitator,\n };\n}\n","/**\n * Dynamic Pricing for x402\n *\n * Calculate prices based on input length (characters, tokens, etc.)\n * Perfect for LLM/AI endpoints where cost scales with input size.\n *\n * @example\n * ```typescript\n * import { createDynamicPricing } from '@dexterai/x402/server';\n *\n * const pricing = createDynamicPricing({\n * unitSize: 1000, // chars per billing unit\n * ratePerUnit: 0.01, // $0.01 per unit\n * minUsd: 0.01, // floor\n * maxUsd: 10.00, // ceiling (optional)\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '23000', usdAmount: 0.023, quoteHash: 'abc...', units: 2.3 }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for dynamic pricing\n */\nexport interface DynamicPricingConfig {\n /**\n * Characters per billing unit.\n * Example: 1000 means every 1000 chars = 1 unit\n */\n unitSize: number;\n\n /**\n * USD per unit.\n * Example: 0.01 means $0.01 per unit\n */\n ratePerUnit: number;\n\n /**\n * Minimum USD amount (floor).\n * Recommended: 0.01 (practical minimum for settlement)\n * @default 0.01\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * Optional - prevents unexpectedly large bills.\n */\n maxUsd?: number;\n\n /**\n * Rounding mode for unit calculation.\n * - 'ceil': Always round up (fair to seller)\n * - 'floor': Always round down (fair to buyer)\n * - 'round': Standard rounding\n * @default 'ceil'\n */\n roundingMode?: 'ceil' | 'floor' | 'round';\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Price quote returned by calculate()\n */\nexport interface PriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount (for display) */\n usdAmount: number;\n\n /**\n * Quote hash for validation.\n * Includes input + config, so config changes invalidate quotes.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n\n /** Number of billing units */\n units: number;\n\n /** Input length in characters */\n inputLength: number;\n}\n\n/**\n * Dynamic pricing calculator\n */\nexport interface DynamicPricing {\n /** Calculate price from input */\n calculate(input: string): PriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Get pricing config (for display) */\n readonly config: Required<DynamicPricingConfig>;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Simple hash function (browser-compatible, no crypto needed)\n * Uses FNV-1a for speed and simplicity\n */\nfunction simpleHash(str: string): string {\n let hash = 2166136261; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 16777619); // FNV prime\n }\n // Convert to hex string\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Create a dynamic pricing calculator\n */\nexport function createDynamicPricing(config: DynamicPricingConfig): DynamicPricing {\n const fullConfig: Required<DynamicPricingConfig> = {\n unitSize: config.unitSize,\n ratePerUnit: config.ratePerUnit,\n minUsd: config.minUsd ?? 0.01,\n maxUsd: config.maxUsd ?? Infinity,\n roundingMode: config.roundingMode ?? 'ceil',\n decimals: config.decimals ?? 6,\n };\n\n const { unitSize, ratePerUnit, minUsd, maxUsd, roundingMode, decimals } = fullConfig;\n\n // Validate config\n if (unitSize <= 0) throw new Error('unitSize must be positive');\n if (ratePerUnit <= 0) throw new Error('ratePerUnit must be positive');\n if (minUsd < 0) throw new Error('minUsd cannot be negative');\n if (maxUsd < minUsd) throw new Error('maxUsd must be >= minUsd');\n\n /**\n * Build hash input string (input + config)\n * Config is included so pricing changes invalidate old quotes\n */\n function buildHashInput(input: string): string {\n const configStr = JSON.stringify({\n unitSize,\n ratePerUnit,\n minUsd,\n maxUsd: maxUsd === Infinity ? 'none' : maxUsd,\n roundingMode,\n });\n return `${input}|${configStr}`;\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string): PriceQuote {\n const inputLength = input.length;\n\n // Calculate units based on rounding mode\n const rawUnits = inputLength / unitSize;\n let units: number;\n switch (roundingMode) {\n case 'ceil':\n units = Math.ceil(rawUnits);\n break;\n case 'floor':\n units = Math.floor(rawUnits);\n break;\n case 'round':\n units = Math.round(rawUnits);\n break;\n }\n\n // Ensure at least 1 unit if there's any input\n if (inputLength > 0 && units === 0) {\n units = 1;\n }\n\n // Calculate USD amount\n let usdAmount = units * ratePerUnit;\n\n // Apply min/max\n usdAmount = Math.max(minUsd, usdAmount);\n usdAmount = Math.min(maxUsd, usdAmount);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash (includes config)\n const quoteHash = simpleHash(buildHashInput(input));\n\n return {\n amountAtomic,\n usdAmount,\n quoteHash,\n units,\n inputLength,\n };\n }\n\n /**\n * Validate quote hash\n * Returns true if the hash matches (input + config unchanged)\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const expectedHash = simpleHash(buildHashInput(input));\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n config: fullConfig,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Format pricing for display\n * Example: \"from $0.01 per 1,000 chars\"\n */\nexport function formatPricing(config: DynamicPricingConfig): string {\n const rate = config.ratePerUnit.toFixed(2);\n const units = config.unitSize.toLocaleString();\n return `from $${rate} per ${units} chars`;\n}\n\n\n\n","/**\n * Token-Based Pricing for x402\n *\n * Accurate LLM pricing using tiktoken for token counting.\n * Uses real OpenAI model rates for precise cost calculation.\n *\n * @example\n * ```typescript\n * import { createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';\n *\n * const pricing = createTokenPricing({\n * model: 'gpt-4o-mini',\n * // Optional overrides:\n * // minUsd: 0.001,\n * // maxUsd: 50.0,\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '1500', usdAmount: 0.0015, inputTokens: 100, quoteHash: 'abc...' }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\nimport { createHash } from 'crypto';\nimport { encoding_for_model, get_encoding, type TiktokenModel } from 'tiktoken';\n\n// ============================================================================\n// Model Pricing Table\n// ============================================================================\n\n/**\n * Pricing info for a model\n */\nexport interface ModelPricing {\n /** USD per 1M input tokens */\n input: number;\n /** USD per 1M output tokens */\n output: number;\n /** USD per 1M cached input tokens (optional) */\n cached?: number;\n /** Default max output tokens for this model */\n maxTokens: number;\n /** Pricing tier */\n tier: 'fast' | 'standard' | 'reasoning' | 'premium';\n}\n\n/**\n * OpenAI Model Pricing - USD per million tokens\n * Updated: December 2024\n */\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // === FAST TIER (cheapest, fastest) ===\n 'gpt-4o-mini': { \n input: 0.15, output: 0.6, cached: 0.075, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-mini': { \n input: 0.4, output: 1.6, cached: 0.1, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-nano': { \n input: 0.1, output: 0.4, cached: 0.025, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-nano': { \n input: 0.05, output: 0.4, cached: 0.005, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-mini': { \n input: 0.25, output: 2.0, cached: 0.025, \n maxTokens: 8192, tier: 'fast' \n },\n\n // === STANDARD TIER (balanced) ===\n 'gpt-4o': { \n input: 2.5, output: 10.0, cached: 1.25, \n maxTokens: 4096, tier: 'standard' \n },\n 'gpt-4.1': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 8192, tier: 'standard' \n },\n 'gpt-5': { \n input: 1.25, output: 10.0, cached: 0.125, \n maxTokens: 8192, tier: 'standard' \n },\n\n // === REASONING TIER (o-series) ===\n 'o1-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o4-mini': { \n input: 1.1, output: 4.4, cached: 0.275, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n 'o1': { \n input: 15.0, output: 60.0, cached: 7.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n\n // === PREMIUM TIER (expensive, specialized) ===\n 'o3-pro': { \n input: 20.0, output: 80.0, \n maxTokens: 32768, tier: 'premium' \n },\n 'o1-pro': { \n input: 150.0, output: 600.0, \n maxTokens: 32768, tier: 'premium' \n },\n};\n\n// Default model for fallback\nconst DEFAULT_MODEL = 'gpt-4o-mini';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for token-based pricing\n */\nexport interface TokenPricingConfig {\n /**\n * Model to use for pricing.\n * Must be a key in MODEL_PRICING or will fallback to gpt-4o-mini.\n */\n model?: string;\n\n /**\n * Minimum USD amount (floor).\n * @default 0.001\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * @default 50.0\n */\n maxUsd?: number;\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Token price quote\n */\nexport interface TokenPriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount */\n usdAmount: number;\n\n /** Number of input tokens */\n inputTokens: number;\n\n /** Model used for pricing */\n model: string;\n\n /** Pricing tier */\n tier: string;\n\n /** Input rate per million tokens */\n inputRatePerMillion: number;\n\n /** Output rate per million tokens */\n outputRatePerMillion: number;\n\n /** Max output tokens for this model */\n maxOutputTokens: number;\n\n /**\n * Quote hash for validation.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n}\n\n/**\n * Token pricing calculator\n */\nexport interface TokenPricing {\n /** Calculate price from input text */\n calculate(input: string, systemPrompt?: string): TokenPriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Count tokens in a string */\n countTokens(input: string): number;\n\n /** Get pricing config */\n readonly config: Required<TokenPricingConfig>;\n\n /** Get model info */\n readonly modelInfo: ModelPricing;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Get tiktoken encoding for a model.\n * Falls back to cl100k_base for unknown models.\n */\nfunction getEncodingForModel(model: string) {\n try {\n return encoding_for_model(model as TiktokenModel);\n } catch {\n // Fall back to cl100k_base (GPT-4/4o family encoding)\n return get_encoding('cl100k_base');\n }\n}\n\n/**\n * Count tokens in a string using tiktoken.\n */\nexport function countTokens(text: string, model: string = DEFAULT_MODEL): number {\n const encoding = getEncodingForModel(model);\n try {\n const tokens = encoding.encode(text);\n return tokens.length;\n } finally {\n encoding.free();\n }\n}\n\n/**\n * Generate a hash of the prompt + pricing config for validation.\n */\nfunction generateQuoteHash(prompt: string, model: string, rate: number, tokens: number): string {\n const configString = JSON.stringify({ model, rate, tokens });\n return createHash('sha256').update(prompt + configString).digest('hex').slice(0, 16);\n}\n\n/**\n * Create a token-based pricing calculator\n */\nexport function createTokenPricing(config: TokenPricingConfig = {}): TokenPricing {\n const model = config.model && MODEL_PRICING[config.model] ? config.model : DEFAULT_MODEL;\n const modelInfo = MODEL_PRICING[model];\n\n const fullConfig: Required<TokenPricingConfig> = {\n model,\n minUsd: config.minUsd ?? 0.001,\n maxUsd: config.maxUsd ?? 50.0,\n decimals: config.decimals ?? 6,\n };\n\n const { minUsd, maxUsd, decimals } = fullConfig;\n\n /**\n * Count tokens in text\n */\n function countTokensInternal(input: string): number {\n return countTokens(input, model);\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string, systemPrompt?: string): TokenPriceQuote {\n // Count input tokens (prompt + system prompt if provided)\n const fullInput = systemPrompt ? `${systemPrompt}\\n\\n${input}` : input;\n const inputTokens = countTokensInternal(fullInput);\n\n // Calculate USD cost based on input tokens only\n // Price = (inputTokens / 1,000,000) × inputRate\n let usdAmount = (inputTokens / 1_000_000) * modelInfo.input;\n\n // Apply min/max caps\n usdAmount = Math.max(usdAmount, minUsd);\n usdAmount = Math.min(usdAmount, maxUsd);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash for validation\n const quoteHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n\n return {\n amountAtomic,\n usdAmount,\n inputTokens,\n model,\n tier: modelInfo.tier,\n inputRatePerMillion: modelInfo.input,\n outputRatePerMillion: modelInfo.output,\n maxOutputTokens: modelInfo.maxTokens,\n quoteHash,\n };\n }\n\n /**\n * Validate quote hash\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const inputTokens = countTokensInternal(input);\n const expectedHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n countTokens: countTokensInternal,\n config: fullConfig,\n modelInfo,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Get list of available models with their pricing.\n */\nexport function getAvailableModels(): Array<{\n model: string;\n inputRate: number;\n outputRate: number;\n maxTokens: number;\n tier: string;\n}> {\n return Object.entries(MODEL_PRICING)\n .map(([model, pricing]) => ({\n model,\n inputRate: pricing.input,\n outputRate: pricing.output,\n maxTokens: pricing.maxTokens,\n tier: pricing.tier,\n }))\n .sort((a, b) => {\n // Sort by tier, then by input rate\n const tierOrder = { fast: 0, standard: 1, reasoning: 2, premium: 3 };\n const tierDiff = tierOrder[a.tier as keyof typeof tierOrder] - tierOrder[b.tier as keyof typeof tierOrder];\n if (tierDiff !== 0) return tierDiff;\n return a.inputRate - b.inputRate;\n });\n}\n\n/**\n * Check if a model exists in our pricing.\n */\nexport function isValidModel(model: string): boolean {\n return model in MODEL_PRICING;\n}\n\n/**\n * Format token pricing for display\n * Example: \"$0.15 per 1M tokens (gpt-4o-mini)\"\n */\nexport function formatTokenPricing(model: string = DEFAULT_MODEL): string {\n const pricing = MODEL_PRICING[model] ?? MODEL_PRICING[DEFAULT_MODEL];\n const actualModel = MODEL_PRICING[model] ? model : DEFAULT_MODEL;\n return `$${pricing.input.toFixed(2)} per 1M tokens (${actualModel})`;\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts","../../src/utils.ts","../../src/server/facilitator-client.ts","../../src/server/x402-server.ts","../../src/server/dynamic-pricing.ts","../../src/server/token-pricing.ts"],"sourcesContent":["/**\n * x402 v2 SDK — Shared Types\n *\n * Chain-agnostic types for x402 v2 payments.\n * Works with Solana, Base, and any future x402-compatible networks.\n */\n\n// ============================================================================\n// Network Constants\n// ============================================================================\n\n/** CAIP-2 network identifier for Solana mainnet */\nexport const SOLANA_MAINNET_NETWORK = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';\n\n/** CAIP-2 network identifier for Base mainnet */\nexport const BASE_MAINNET_NETWORK = 'eip155:8453';\n\n/** Alias for Solana mainnet */\nexport const SOLANA_MAINNET = SOLANA_MAINNET_NETWORK;\n\n/** Alias for Base mainnet */\nexport const BASE_MAINNET = BASE_MAINNET_NETWORK;\n\n// ============================================================================\n// Asset Constants\n// ============================================================================\n\n/** USDC mint on Solana mainnet */\nexport const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\n\n/** USDC address on Base mainnet */\nexport const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';\n\n// ============================================================================\n// Facilitator Constants\n// ============================================================================\n\n/** Dexter's public x402 v2 facilitator URL */\nexport const DEXTER_FACILITATOR_URL = 'https://x402.dexter.cash';\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/**\n * Asset configuration for payments\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n /** Optional: Human-readable symbol */\n symbol?: string;\n}\n\n/**\n * Resource info included in payment requirements\n */\nexport interface ResourceInfo {\n /** Resource URL */\n url: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the resource */\n mimeType?: string;\n}\n\n/**\n * Extra fields in payment requirements\n * Chain-specific fields may vary\n */\nexport interface AcceptsExtra {\n /** Facilitator address that pays tx fees (required for Solana) */\n feePayer?: string;\n /** Token decimals (optional - defaults to 6 for USDC) */\n decimals?: number;\n /** EIP-712: Token name (EVM only) */\n name?: string;\n /** EIP-712: Token version (EVM only) */\n version?: string;\n /** Additional chain-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * A single payment option in the accepts array\n */\nexport interface PaymentAccept {\n /** Payment scheme (always 'exact' for x402 v2) */\n scheme: 'exact';\n /** CAIP-2 network identifier */\n network: string;\n /** Payment amount in atomic units (x402 spec field - REQUIRED) */\n maxAmountRequired: string;\n /** Alias for maxAmountRequired (for convenience) */\n amount?: string;\n /** Token address */\n asset: string;\n /** Seller's address to receive payment */\n payTo: string;\n /** Maximum seconds until payment expires */\n maxTimeoutSeconds: number;\n /** Chain-specific extra data */\n extra: AcceptsExtra;\n}\n\n/**\n * Full PaymentRequired structure (sent in PAYMENT-REQUIRED header)\n */\nexport interface PaymentRequired {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** Available payment options */\n accepts: PaymentAccept[];\n /** Optional error message */\n error?: string;\n}\n\n/**\n * PaymentSignature structure (sent in PAYMENT-SIGNATURE header)\n */\nexport interface PaymentSignature {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** The payment option that was accepted */\n accepted: PaymentAccept;\n /** The signed payment */\n payload: {\n /** Signed transaction (base64 for Solana, JSON for EVM) */\n transaction: string;\n };\n}\n\n// ============================================================================\n// Facilitator Response Types\n// ============================================================================\n\n/**\n * Response from /verify endpoint\n */\nexport interface VerifyResponse {\n /** Whether the payment is valid */\n isValid: boolean;\n /** Reason for invalidity (if invalid) */\n invalidReason?: string;\n /** Payer address */\n payer?: string;\n}\n\n/**\n * Response from /settle endpoint\n */\nexport interface SettleResponse {\n /** Whether settlement succeeded */\n success: boolean;\n /** Transaction signature/hash */\n transaction?: string;\n /** Network the payment was made on */\n network: string;\n /** Error reason (if failed) */\n errorReason?: string;\n /** Error code (if failed) */\n errorCode?: string;\n /** Payer address */\n payer?: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * SDK error codes\n */\nexport type X402ErrorCode =\n // Client errors\n | 'missing_payment_required_header'\n | 'invalid_payment_required'\n | 'unsupported_network'\n | 'no_matching_payment_option'\n | 'no_solana_accept' // Legacy, kept for compatibility\n | 'missing_fee_payer'\n | 'missing_decimals'\n | 'missing_amount'\n | 'amount_exceeds_max'\n | 'insufficient_balance'\n | 'wallet_missing_sign_transaction'\n | 'wallet_not_connected'\n | 'transaction_build_failed'\n | 'payment_rejected'\n // Server errors\n | 'invalid_payment_signature'\n | 'facilitator_verify_failed'\n | 'facilitator_settle_failed'\n | 'facilitator_request_failed'\n | 'no_matching_requirement';\n\n/**\n * Custom error class for x402 operations\n */\nexport class X402Error extends Error {\n /** Error code for programmatic handling */\n code: X402ErrorCode;\n /** Additional error details */\n details?: unknown;\n\n constructor(code: X402ErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'X402Error';\n this.code = code;\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, X402Error.prototype);\n }\n}\n","/**\n * Utility Functions\n *\n * Chain-agnostic helpers for x402 payments.\n */\n\nimport { SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK } from './types';\n\n// ============================================================================\n// Amount Conversion\n// ============================================================================\n\n/**\n * Convert human-readable amount to atomic units\n *\n * @param amount - Human-readable amount (e.g., 0.05 for $0.05)\n * @param decimals - Token decimals (e.g., 6 for USDC)\n * @returns Amount in atomic units as string\n *\n * @example\n * ```typescript\n * toAtomicUnits(0.05, 6) // '50000'\n * toAtomicUnits(1.50, 6) // '1500000'\n * ```\n */\nexport function toAtomicUnits(amount: number, decimals: number): string {\n const multiplier = Math.pow(10, decimals);\n return Math.floor(amount * multiplier).toString();\n}\n\n/**\n * Convert atomic units to human-readable amount\n *\n * @param atomicUnits - Amount in smallest units\n * @param decimals - Token decimals\n * @returns Human-readable amount\n *\n * @example\n * ```typescript\n * fromAtomicUnits('50000', 6) // 0.05\n * fromAtomicUnits(1500000n, 6) // 1.5\n * ```\n */\nexport function fromAtomicUnits(\n atomicUnits: string | bigint | number,\n decimals: number\n): number {\n const divisor = Math.pow(10, decimals);\n return Number(atomicUnits) / divisor;\n}\n\n// ============================================================================\n// Network Helpers\n// ============================================================================\n\n/**\n * Network type\n */\nexport type ChainFamily = 'solana' | 'evm' | 'unknown';\n\n/**\n * Get the chain family from a CAIP-2 network identifier\n *\n * @param network - CAIP-2 network identifier\n * @returns Chain family\n *\n * @example\n * ```typescript\n * getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'\n * getChainFamily('eip155:8453') // 'evm'\n * ```\n */\nexport function getChainFamily(network: string): ChainFamily {\n if (network.startsWith('solana:') || network === 'solana') {\n return 'solana';\n }\n if (network.startsWith('eip155:') || ['base', 'ethereum', 'arbitrum'].includes(network)) {\n return 'evm';\n }\n return 'unknown';\n}\n\n/**\n * Get default RPC URL for a network\n *\n * @param network - CAIP-2 network identifier\n * @returns Default RPC URL\n */\nexport function getDefaultRpcUrl(network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n if (network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1') {\n return 'https://api.devnet.solana.com';\n }\n if (network.includes('testnet') || network === 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z') {\n return 'https://api.testnet.solana.com';\n }\n // Mainnet uses Dexter's RPC proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n }\n\n if (family === 'evm') {\n // Extract chain ID from CAIP-2\n if (network.startsWith('eip155:')) {\n const chainId = network.split(':')[1];\n switch (chainId) {\n case '8453': return 'https://api.dexter.cash/api/base/rpc'; // Dexter proxy\n case '84532': return 'https://sepolia.base.org';\n case '1': return 'https://eth.llamarpc.com';\n case '42161': return 'https://arb1.arbitrum.io/rpc';\n default: return 'https://api.dexter.cash/api/base/rpc';\n }\n }\n // Legacy names\n if (network === 'base') return 'https://api.dexter.cash/api/base/rpc';\n if (network === 'ethereum') return 'https://eth.llamarpc.com';\n if (network === 'arbitrum') return 'https://arb1.arbitrum.io/rpc';\n return 'https://api.dexter.cash/api/base/rpc';\n }\n\n // Unknown - return Dexter's Solana proxy\n return 'https://api.dexter.cash/api/solana/rpc';\n}\n\n/**\n * Get human-readable chain name\n *\n * @param network - CAIP-2 network identifier\n * @returns Human-readable name\n */\nexport function getChainName(network: string): string {\n const mapping: Record<string, string> = {\n [SOLANA_MAINNET_NETWORK]: 'Solana',\n 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'Solana Devnet',\n 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'Solana Testnet',\n 'solana': 'Solana',\n [BASE_MAINNET_NETWORK]: 'Base',\n 'eip155:84532': 'Base Sepolia',\n 'eip155:1': 'Ethereum',\n 'eip155:42161': 'Arbitrum One',\n 'base': 'Base',\n 'ethereum': 'Ethereum',\n 'arbitrum': 'Arbitrum',\n };\n return mapping[network] || network;\n}\n\n// ============================================================================\n// Transaction URL Helpers\n// ============================================================================\n\n/**\n * Get explorer URL for a transaction\n *\n * @param txSignature - Transaction signature/hash\n * @param network - CAIP-2 network identifier\n * @returns Explorer URL\n */\nexport function getExplorerUrl(txSignature: string, network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n const isDevnet = network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';\n if (isDevnet) {\n return `https://solscan.io/tx/${txSignature}?cluster=devnet`;\n }\n // Prefer Orb Markets for mainnet\n return `https://www.orbmarkets.io/tx/${txSignature}`;\n }\n\n if (family === 'evm') {\n // Extract chain ID\n let chainId = '8453'; // Default to Base\n if (network.startsWith('eip155:')) {\n chainId = network.split(':')[1];\n } else if (network === 'ethereum') {\n chainId = '1';\n } else if (network === 'arbitrum') {\n chainId = '42161';\n }\n\n switch (chainId) {\n case '8453': return `https://basescan.org/tx/${txSignature}`;\n case '84532': return `https://sepolia.basescan.org/tx/${txSignature}`;\n case '1': return `https://etherscan.io/tx/${txSignature}`;\n case '42161': return `https://arbiscan.io/tx/${txSignature}`;\n default: return `https://basescan.org/tx/${txSignature}`;\n }\n }\n\n return `https://solscan.io/tx/${txSignature}`;\n}\n\n// ============================================================================\n// Encoding Helpers\n// ============================================================================\n\n/**\n * Encode an object as base64 JSON\n */\nexport function encodeBase64Json(obj: unknown): string {\n return btoa(JSON.stringify(obj));\n}\n\n/**\n * Decode base64 JSON to object\n */\nexport function decodeBase64Json<T>(encoded: string): T {\n return JSON.parse(atob(encoded)) as T;\n}\n","/**\n * Facilitator Client\n *\n * Communicates with the x402 facilitator for:\n * - /supported - Get supported payment schemes and fee payer addresses\n * - /verify - Verify a payment signature before processing\n * - /settle - Submit the payment for execution\n *\n * Works with any x402 v2 facilitator (Dexter or others).\n */\n\nimport type { PaymentAccept, PaymentSignature, VerifyResponse, SettleResponse } from '../types';\nimport { DEXTER_FACILITATOR_URL } from '../types';\nimport { decodeBase64Json } from '../utils';\n\n/**\n * Supported payment kind from facilitator /supported endpoint\n */\nexport interface SupportedKind {\n x402Version: number;\n scheme: string;\n network: string;\n extra?: {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n };\n}\n\n/**\n * Response from facilitator /supported endpoint\n */\nexport interface SupportedResponse {\n kinds: SupportedKind[];\n}\n\n/**\n * Client for communicating with an x402 v2 facilitator\n */\nexport class FacilitatorClient {\n private facilitatorUrl: string;\n private cachedSupported: SupportedResponse | null = null;\n private cacheTime: number = 0;\n private readonly CACHE_TTL_MS = 60_000; // 1 minute cache\n\n constructor(facilitatorUrl: string = DEXTER_FACILITATOR_URL) {\n this.facilitatorUrl = facilitatorUrl.replace(/\\/$/, ''); // Remove trailing slash\n }\n\n /**\n * Get supported payment kinds from the facilitator\n * Results are cached for 1 minute to reduce network calls\n */\n async getSupported(): Promise<SupportedResponse> {\n const now = Date.now();\n if (this.cachedSupported && now - this.cacheTime < this.CACHE_TTL_MS) {\n return this.cachedSupported;\n }\n\n const response = await fetch(`${this.facilitatorUrl}/supported`);\n if (!response.ok) {\n throw new Error(`Facilitator /supported returned ${response.status}`);\n }\n\n this.cachedSupported = (await response.json()) as SupportedResponse;\n this.cacheTime = now;\n return this.cachedSupported;\n }\n\n /**\n * Get the fee payer address for a specific network\n *\n * @param network - CAIP-2 network identifier\n * @returns Fee payer address\n */\n async getFeePayer(network: string): Promise<string> {\n const supported = await this.getSupported();\n\n // Find matching network support (exact match or v2)\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n if (!kind?.extra?.feePayer) {\n throw new Error(\n `Facilitator does not support network \"${network}\" with scheme \"exact\", or feePayer not provided`\n );\n }\n\n return kind.extra.feePayer;\n }\n\n /**\n * Get extra data for a network (feePayer, decimals, EIP-712 data, etc.)\n *\n * @param network - CAIP-2 network identifier\n * @returns Extra data from /supported\n */\n async getNetworkExtra(network: string): Promise<SupportedKind['extra']> {\n const supported = await this.getSupported();\n\n const kind = supported.kinds.find(\n (k) =>\n k.x402Version === 2 &&\n k.scheme === 'exact' &&\n k.network === network\n );\n\n return kind?.extra;\n }\n\n /**\n * Verify a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Verification response\n */\n async verifyPayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<VerifyResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const verifyPayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(verifyPayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /verify returned ${response.status}:`, errorText);\n return {\n isValid: false,\n invalidReason: `facilitator_error_${response.status}`,\n };\n }\n\n return (await response.json()) as VerifyResponse;\n } catch (error) {\n console.error('Payment verification failed:', error);\n return {\n isValid: false,\n invalidReason: error instanceof Error ? error.message : 'unexpected_verify_error',\n };\n }\n }\n\n /**\n * Settle a payment with the facilitator\n *\n * @param paymentSignatureHeader - Base64-encoded PAYMENT-SIGNATURE header value\n * @param requirements - The payment requirements that were sent to the client\n * @returns Settlement response with transaction signature on success\n */\n async settlePayment(\n paymentSignatureHeader: string,\n requirements: PaymentAccept\n ): Promise<SettleResponse> {\n try {\n const paymentPayload = decodeBase64Json<PaymentSignature>(paymentSignatureHeader);\n\n const settlePayload = {\n paymentPayload,\n paymentRequirements: requirements,\n };\n\n const response = await fetch(`${this.facilitatorUrl}/settle`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(settlePayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n console.error(`Facilitator /settle returned ${response.status}:`, errorText);\n return {\n success: false,\n network: requirements.network,\n errorReason: `facilitator_error_${response.status}`,\n };\n }\n\n const result = (await response.json()) as SettleResponse;\n return {\n ...result,\n network: requirements.network,\n };\n } catch (error) {\n console.error('Payment settlement failed:', error);\n return {\n success: false,\n network: requirements.network,\n errorReason: error instanceof Error ? error.message : 'unexpected_settle_error',\n };\n }\n }\n}\n","/**\n * x402 v2 Server\n *\n * Server-side helpers for accepting x402 payments.\n * Chain-agnostic - works with Solana, Base, and any x402-compatible network.\n *\n * @example\n * ```typescript\n * import { createX402Server } from '@dexterai/x402/server';\n *\n * const server = createX402Server({\n * payTo: 'YourAddress...',\n * network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n * });\n *\n * // Handle 402 responses\n * if (!paymentSignature) {\n * const requirements = await server.buildRequirements({\n * amountAtomic: '50000',\n * resourceUrl: '/api/protected',\n * });\n * res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));\n * res.status(402).json({});\n * return;\n * }\n *\n * // Verify and settle\n * const verify = await server.verifyPayment(paymentSignature);\n * if (!verify.isValid) throw new Error(verify.invalidReason);\n *\n * const settle = await server.settlePayment(paymentSignature);\n * if (!settle.success) throw new Error(settle.errorReason);\n *\n * // Payment successful!\n * res.json({ transaction: settle.transaction });\n * ```\n */\n\nimport type {\n PaymentRequired,\n PaymentAccept,\n ResourceInfo,\n AcceptsExtra,\n VerifyResponse,\n SettleResponse,\n} from '../types';\nimport {\n SOLANA_MAINNET_NETWORK,\n USDC_MINT,\n DEXTER_FACILITATOR_URL,\n} from '../types';\nimport { FacilitatorClient, type SupportedKind } from './facilitator-client';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Asset configuration\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n}\n\n/**\n * Server configuration\n */\nexport interface X402ServerConfig {\n /** Address to receive payments */\n payTo: string;\n /** Facilitator URL (defaults to Dexter) */\n facilitatorUrl?: string;\n /** CAIP-2 network identifier */\n network?: string;\n /** Asset configuration (defaults to USDC) */\n asset?: AssetConfig;\n /** Default payment timeout in seconds */\n defaultTimeoutSeconds?: number;\n}\n\n/**\n * Options for building payment requirements\n */\nexport interface BuildRequirementsOptions {\n /** Amount in atomic units (e.g., '50000' for 0.05 USDC) */\n amountAtomic: string;\n /** Full URL of the resource */\n resourceUrl: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the response */\n mimeType?: string;\n /** Override timeout for this request */\n timeoutSeconds?: number;\n}\n\n/**\n * x402 Server interface\n */\nexport interface X402Server {\n /** Build payment requirements (fetches feePayer from facilitator) */\n buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired>;\n\n /** Encode requirements for PAYMENT-REQUIRED header */\n encodeRequirements(requirements: PaymentRequired): string;\n\n /** Create complete 402 response object */\n create402Response(requirements: PaymentRequired): {\n status: 402;\n headers: { 'PAYMENT-REQUIRED': string };\n body: Record<string, unknown>;\n };\n\n /** Verify payment with facilitator */\n verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse>;\n\n /** Settle payment via facilitator */\n settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse>;\n\n /** Get PaymentAccept for verify/settle */\n getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept>;\n\n /** Get network this server is configured for */\n readonly network: string;\n\n /** Get facilitator client for advanced usage */\n readonly facilitator: FacilitatorClient;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Create an x402 server for accepting payments\n */\nexport function createX402Server(config: X402ServerConfig): X402Server {\n const {\n payTo,\n facilitatorUrl = DEXTER_FACILITATOR_URL,\n network = SOLANA_MAINNET_NETWORK,\n asset = { address: USDC_MINT, decimals: 6 },\n defaultTimeoutSeconds = 60,\n } = config;\n\n const facilitator = new FacilitatorClient(facilitatorUrl);\n\n // Cache for network extra data\n let cachedExtra: SupportedKind['extra'] | null = null;\n\n /**\n * Get extra data from facilitator (cached)\n */\n async function getNetworkExtra(): Promise<AcceptsExtra> {\n if (!cachedExtra) {\n cachedExtra = await facilitator.getNetworkExtra(network);\n }\n\n if (!cachedExtra?.feePayer) {\n throw new Error(`Facilitator does not provide feePayer for network \"${network}\"`);\n }\n\n return {\n feePayer: cachedExtra.feePayer,\n decimals: cachedExtra.decimals ?? asset.decimals,\n // Include any additional EIP-712 data for EVM chains\n name: cachedExtra.name,\n version: cachedExtra.version,\n };\n }\n\n /**\n * Build a PaymentAccept structure\n */\n async function getPaymentAccept(options: BuildRequirementsOptions): Promise<PaymentAccept> {\n const {\n amountAtomic,\n timeoutSeconds = defaultTimeoutSeconds,\n } = options;\n\n const extra = await getNetworkExtra();\n\n return {\n scheme: 'exact',\n network,\n maxAmountRequired: amountAtomic,\n asset: asset.address,\n payTo,\n maxTimeoutSeconds: timeoutSeconds,\n extra,\n };\n }\n\n /**\n * Build payment requirements for a 402 response\n */\n async function buildRequirements(options: BuildRequirementsOptions): Promise<PaymentRequired> {\n const {\n resourceUrl,\n description,\n mimeType = 'application/json',\n } = options;\n\n const resource: ResourceInfo = {\n url: resourceUrl,\n description,\n mimeType,\n };\n\n const accept = await getPaymentAccept(options);\n\n return {\n x402Version: 2,\n resource,\n accepts: [accept],\n error: 'Payment required',\n };\n }\n\n /**\n * Encode requirements for PAYMENT-REQUIRED header\n */\n function encodeRequirements(requirements: PaymentRequired): string {\n return btoa(JSON.stringify(requirements));\n }\n\n /**\n * Create complete 402 response object\n */\n function create402Response(requirements: PaymentRequired) {\n return {\n status: 402 as const,\n headers: {\n 'PAYMENT-REQUIRED': encodeRequirements(requirements),\n },\n body: {},\n };\n }\n\n /**\n * Verify payment with facilitator\n */\n async function verifyPayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<VerifyResponse> {\n // If requirements not provided, build default\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.verifyPayment(paymentSignatureHeader, req);\n }\n\n /**\n * Settle payment via facilitator\n */\n async function settlePayment(\n paymentSignatureHeader: string,\n requirements?: PaymentAccept\n ): Promise<SettleResponse> {\n const req = requirements || await getPaymentAccept({\n amountAtomic: '0',\n resourceUrl: '',\n });\n\n return facilitator.settlePayment(paymentSignatureHeader, req);\n }\n\n return {\n buildRequirements,\n encodeRequirements,\n create402Response,\n verifyPayment,\n settlePayment,\n getPaymentAccept,\n network,\n facilitator,\n };\n}\n","/**\n * Dynamic Pricing for x402\n *\n * Calculate prices based on input length (characters, tokens, etc.)\n * Perfect for LLM/AI endpoints where cost scales with input size.\n *\n * @example\n * ```typescript\n * import { createDynamicPricing } from '@dexterai/x402/server';\n *\n * const pricing = createDynamicPricing({\n * unitSize: 1000, // chars per billing unit\n * ratePerUnit: 0.01, // $0.01 per unit\n * minUsd: 0.01, // floor\n * maxUsd: 10.00, // ceiling (optional)\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '23000', usdAmount: 0.023, quoteHash: 'abc...', units: 2.3 }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for dynamic pricing\n */\nexport interface DynamicPricingConfig {\n /**\n * Characters per billing unit.\n * Example: 1000 means every 1000 chars = 1 unit\n */\n unitSize: number;\n\n /**\n * USD per unit.\n * Example: 0.01 means $0.01 per unit\n */\n ratePerUnit: number;\n\n /**\n * Minimum USD amount (floor).\n * Recommended: 0.01 (practical minimum for settlement)\n * @default 0.01\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * Optional - prevents unexpectedly large bills.\n */\n maxUsd?: number;\n\n /**\n * Rounding mode for unit calculation.\n * - 'ceil': Always round up (fair to seller)\n * - 'floor': Always round down (fair to buyer)\n * - 'round': Standard rounding\n * @default 'ceil'\n */\n roundingMode?: 'ceil' | 'floor' | 'round';\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Price quote returned by calculate()\n */\nexport interface PriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount (for display) */\n usdAmount: number;\n\n /**\n * Quote hash for validation.\n * Includes input + config, so config changes invalidate quotes.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n\n /** Number of billing units */\n units: number;\n\n /** Input length in characters */\n inputLength: number;\n}\n\n/**\n * Dynamic pricing calculator\n */\nexport interface DynamicPricing {\n /** Calculate price from input */\n calculate(input: string): PriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Get pricing config (for display) */\n readonly config: Required<DynamicPricingConfig>;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Simple hash function (browser-compatible, no crypto needed)\n * Uses FNV-1a for speed and simplicity\n */\nfunction simpleHash(str: string): string {\n let hash = 2166136261; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 16777619); // FNV prime\n }\n // Convert to hex string\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Create a dynamic pricing calculator\n */\nexport function createDynamicPricing(config: DynamicPricingConfig): DynamicPricing {\n const fullConfig: Required<DynamicPricingConfig> = {\n unitSize: config.unitSize,\n ratePerUnit: config.ratePerUnit,\n minUsd: config.minUsd ?? 0.01,\n maxUsd: config.maxUsd ?? Infinity,\n roundingMode: config.roundingMode ?? 'ceil',\n decimals: config.decimals ?? 6,\n };\n\n const { unitSize, ratePerUnit, minUsd, maxUsd, roundingMode, decimals } = fullConfig;\n\n // Validate config\n if (unitSize <= 0) throw new Error('unitSize must be positive');\n if (ratePerUnit <= 0) throw new Error('ratePerUnit must be positive');\n if (minUsd < 0) throw new Error('minUsd cannot be negative');\n if (maxUsd < minUsd) throw new Error('maxUsd must be >= minUsd');\n\n /**\n * Build hash input string (input + config)\n * Config is included so pricing changes invalidate old quotes\n */\n function buildHashInput(input: string): string {\n const configStr = JSON.stringify({\n unitSize,\n ratePerUnit,\n minUsd,\n maxUsd: maxUsd === Infinity ? 'none' : maxUsd,\n roundingMode,\n });\n return `${input}|${configStr}`;\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string): PriceQuote {\n const inputLength = input.length;\n\n // Calculate units based on rounding mode\n const rawUnits = inputLength / unitSize;\n let units: number;\n switch (roundingMode) {\n case 'ceil':\n units = Math.ceil(rawUnits);\n break;\n case 'floor':\n units = Math.floor(rawUnits);\n break;\n case 'round':\n units = Math.round(rawUnits);\n break;\n }\n\n // Ensure at least 1 unit if there's any input\n if (inputLength > 0 && units === 0) {\n units = 1;\n }\n\n // Calculate USD amount\n let usdAmount = units * ratePerUnit;\n\n // Apply min/max\n usdAmount = Math.max(minUsd, usdAmount);\n usdAmount = Math.min(maxUsd, usdAmount);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash (includes config)\n const quoteHash = simpleHash(buildHashInput(input));\n\n return {\n amountAtomic,\n usdAmount,\n quoteHash,\n units,\n inputLength,\n };\n }\n\n /**\n * Validate quote hash\n * Returns true if the hash matches (input + config unchanged)\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const expectedHash = simpleHash(buildHashInput(input));\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n config: fullConfig,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Format pricing for display\n * Example: \"from $0.01 per 1,000 chars\"\n */\nexport function formatPricing(config: DynamicPricingConfig): string {\n const rate = config.ratePerUnit.toFixed(2);\n const units = config.unitSize.toLocaleString();\n return `from $${rate} per ${units} chars`;\n}\n\n\n\n","/**\n * Token-Based Pricing for x402\n *\n * Accurate LLM pricing using tiktoken for token counting.\n * Uses real OpenAI model rates for precise cost calculation.\n *\n * @example\n * ```typescript\n * import { createTokenPricing, MODEL_PRICING } from '@dexterai/x402/server';\n *\n * const pricing = createTokenPricing({\n * model: 'gpt-4o-mini',\n * // Optional overrides:\n * // minUsd: 0.001,\n * // maxUsd: 50.0,\n * });\n *\n * // Calculate price from input\n * const quote = pricing.calculate(userPrompt);\n * // → { amountAtomic: '1500', usdAmount: 0.0015, inputTokens: 100, quoteHash: 'abc...' }\n *\n * // Validate on retry (prevents prompt manipulation)\n * const isValid = pricing.validateQuote(userPrompt, req.headers['x-quote-hash']);\n * ```\n */\n\nimport { createHash } from 'crypto';\nimport { encoding_for_model, get_encoding, type TiktokenModel } from 'tiktoken';\n\n// ============================================================================\n// Model Pricing Table\n// ============================================================================\n\n/**\n * Pricing info for a model\n */\nexport interface ModelPricing {\n /** USD per 1M input tokens */\n input: number;\n /** USD per 1M output tokens */\n output: number;\n /** USD per 1M cached input tokens (optional) */\n cached?: number;\n /** Default max output tokens for this model */\n maxTokens: number;\n /** Pricing tier */\n tier: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';\n}\n\n/**\n * OpenAI Model Pricing - USD per million tokens\n * Updated: December 2024\n */\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // === FAST TIER (cheapest, fastest) ===\n 'gpt-4o-mini': { \n input: 0.15, output: 0.6, cached: 0.075, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-mini': { \n input: 0.4, output: 1.6, cached: 0.1, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-4.1-nano': { \n input: 0.1, output: 0.4, cached: 0.025, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-nano': { \n input: 0.05, output: 0.4, cached: 0.005, \n maxTokens: 4096, tier: 'fast' \n },\n 'gpt-5-mini': { \n input: 0.25, output: 2.0, cached: 0.025, \n maxTokens: 8192, tier: 'fast' \n },\n\n // === STANDARD TIER (balanced) ===\n 'gpt-4o': { \n input: 2.5, output: 10.0, cached: 1.25, \n maxTokens: 4096, tier: 'standard' \n },\n 'gpt-4.1': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 8192, tier: 'standard' \n },\n 'gpt-5': { \n input: 1.25, output: 10.0, cached: 0.125, \n maxTokens: 8192, tier: 'standard' \n },\n\n // === REASONING TIER (o-series) ===\n 'o1-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3-mini': { \n input: 1.1, output: 4.4, cached: 0.55, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o4-mini': { \n input: 1.1, output: 4.4, cached: 0.275, \n maxTokens: 16384, tier: 'reasoning' \n },\n 'o3': { \n input: 2.0, output: 8.0, cached: 0.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n 'o1': { \n input: 15.0, output: 60.0, cached: 7.5, \n maxTokens: 32768, tier: 'reasoning' \n },\n\n // === PREMIUM TIER (expensive, specialized) ===\n 'o3-pro': { \n input: 20.0, output: 80.0, \n maxTokens: 32768, tier: 'premium' \n },\n 'o1-pro': { \n input: 150.0, output: 600.0, \n maxTokens: 32768, tier: 'premium' \n },\n};\n\n// Default model for fallback\nconst DEFAULT_MODEL = 'gpt-4o-mini';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Configuration for token-based pricing\n */\nexport interface TokenPricingConfig {\n /**\n * Model name. Used for display and to look up built-in pricing.\n * If not in MODEL_PRICING and no custom rates provided, falls back to gpt-4o-mini rates.\n */\n model?: string;\n\n /**\n * Custom input rate (USD per 1M tokens).\n * Overrides built-in MODEL_PRICING for this model.\n * Use this for Anthropic, Gemini, Mistral, or any custom model.\n */\n inputRate?: number;\n\n /**\n * Custom output rate (USD per 1M tokens).\n * Optional - used for display/info purposes.\n */\n outputRate?: number;\n\n /**\n * Custom max output tokens.\n * @default 4096\n */\n maxTokens?: number;\n\n /**\n * Custom tier label.\n * @default 'custom'\n */\n tier?: 'fast' | 'standard' | 'reasoning' | 'premium' | 'custom';\n\n /**\n * Custom tokenizer function.\n * If not provided, uses tiktoken (cl100k_base encoding).\n * Use this for models with different tokenization (e.g., Llama, Mistral).\n * \n * @example\n * tokenizer: (text) => llamaTokenizer.encode(text).length\n */\n tokenizer?: (text: string) => number;\n\n /**\n * Minimum USD amount (floor).\n * @default 0.001\n */\n minUsd?: number;\n\n /**\n * Maximum USD amount (ceiling).\n * @default 50.0\n */\n maxUsd?: number;\n\n /**\n * Token decimals for atomic conversion.\n * @default 6 (USDC)\n */\n decimals?: number;\n}\n\n/**\n * Token price quote\n */\nexport interface TokenPriceQuote {\n /** Amount in atomic units (for buildRequirements) */\n amountAtomic: string;\n\n /** Human-readable USD amount */\n usdAmount: number;\n\n /** Number of input tokens */\n inputTokens: number;\n\n /** Model used for pricing */\n model: string;\n\n /** Pricing tier */\n tier: string;\n\n /** Input rate per million tokens */\n inputRatePerMillion: number;\n\n /** Output rate per million tokens */\n outputRatePerMillion: number;\n\n /** Max output tokens for this model */\n maxOutputTokens: number;\n\n /**\n * Quote hash for validation.\n * Client should send this back as X-Quote-Hash header.\n */\n quoteHash: string;\n}\n\n/**\n * Token pricing calculator\n */\nexport interface TokenPricing {\n /** Calculate price from input text */\n calculate(input: string, systemPrompt?: string): TokenPriceQuote;\n\n /** Validate quote hash (returns true if valid) */\n validateQuote(input: string, quoteHash: string): boolean;\n\n /** Count tokens in a string */\n countTokens(input: string): number;\n\n /** Get pricing config */\n readonly config: Required<TokenPricingConfig>;\n\n /** Get model info */\n readonly modelInfo: ModelPricing;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Get tiktoken encoding for a model.\n * Falls back to cl100k_base for unknown models.\n */\nfunction getEncodingForModel(model: string) {\n try {\n return encoding_for_model(model as TiktokenModel);\n } catch {\n // Fall back to cl100k_base (GPT-4/4o family encoding)\n return get_encoding('cl100k_base');\n }\n}\n\n/**\n * Count tokens in a string using tiktoken.\n */\nexport function countTokens(text: string, model: string = DEFAULT_MODEL): number {\n const encoding = getEncodingForModel(model);\n try {\n const tokens = encoding.encode(text);\n return tokens.length;\n } finally {\n encoding.free();\n }\n}\n\n/**\n * Generate a hash of the prompt + pricing config for validation.\n */\nfunction generateQuoteHash(prompt: string, model: string, rate: number, tokens: number): string {\n const configString = JSON.stringify({ model, rate, tokens });\n return createHash('sha256').update(prompt + configString).digest('hex').slice(0, 16);\n}\n\n/**\n * Create a token-based pricing calculator\n */\nexport function createTokenPricing(config: TokenPricingConfig = {}): TokenPricing {\n // Determine model name\n const model = config.model ?? DEFAULT_MODEL;\n \n // Get built-in pricing if available, otherwise use custom or default\n const builtInPricing = MODEL_PRICING[model];\n \n // Build effective model info (custom rates override built-in)\n const modelInfo: ModelPricing = {\n input: config.inputRate ?? builtInPricing?.input ?? MODEL_PRICING[DEFAULT_MODEL].input,\n output: config.outputRate ?? builtInPricing?.output ?? MODEL_PRICING[DEFAULT_MODEL].output,\n maxTokens: config.maxTokens ?? builtInPricing?.maxTokens ?? 4096,\n tier: config.tier ?? builtInPricing?.tier ?? 'custom',\n };\n\n // Custom tokenizer or default tiktoken\n const customTokenizer = config.tokenizer;\n\n const fullConfig: Required<TokenPricingConfig> = {\n model,\n inputRate: modelInfo.input,\n outputRate: modelInfo.output,\n maxTokens: modelInfo.maxTokens,\n tier: modelInfo.tier,\n tokenizer: customTokenizer ?? ((text: string) => countTokens(text, model)),\n minUsd: config.minUsd ?? 0.001,\n maxUsd: config.maxUsd ?? 50.0,\n decimals: config.decimals ?? 6,\n };\n\n const { minUsd, maxUsd, decimals } = fullConfig;\n\n /**\n * Count tokens in text (uses custom tokenizer if provided)\n */\n function countTokensInternal(input: string): number {\n if (customTokenizer) {\n return customTokenizer(input);\n }\n return countTokens(input, model);\n }\n\n /**\n * Calculate price from input\n */\n function calculate(input: string, systemPrompt?: string): TokenPriceQuote {\n // Count input tokens (prompt + system prompt if provided)\n const fullInput = systemPrompt ? `${systemPrompt}\\n\\n${input}` : input;\n const inputTokens = countTokensInternal(fullInput);\n\n // Calculate USD cost based on input tokens only\n // Price = (inputTokens / 1,000,000) × inputRate\n let usdAmount = (inputTokens / 1_000_000) * modelInfo.input;\n\n // Apply min/max caps\n usdAmount = Math.max(usdAmount, minUsd);\n usdAmount = Math.min(usdAmount, maxUsd);\n\n // Convert to atomic units\n const multiplier = Math.pow(10, decimals);\n const amountAtomic = Math.floor(usdAmount * multiplier).toString();\n\n // Generate quote hash for validation\n const quoteHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n\n return {\n amountAtomic,\n usdAmount,\n inputTokens,\n model,\n tier: modelInfo.tier,\n inputRatePerMillion: modelInfo.input,\n outputRatePerMillion: modelInfo.output,\n maxOutputTokens: modelInfo.maxTokens,\n quoteHash,\n };\n }\n\n /**\n * Validate quote hash\n */\n function validateQuote(input: string, quoteHash: string): boolean {\n if (!quoteHash) return false;\n const inputTokens = countTokensInternal(input);\n const expectedHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);\n return expectedHash === quoteHash;\n }\n\n return {\n calculate,\n validateQuote,\n countTokens: countTokensInternal,\n config: fullConfig,\n modelInfo,\n };\n}\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Get list of available models with their pricing.\n */\nexport function getAvailableModels(): Array<{\n model: string;\n inputRate: number;\n outputRate: number;\n maxTokens: number;\n tier: string;\n}> {\n return Object.entries(MODEL_PRICING)\n .map(([model, pricing]) => ({\n model,\n inputRate: pricing.input,\n outputRate: pricing.output,\n maxTokens: pricing.maxTokens,\n tier: pricing.tier,\n }))\n .sort((a, b) => {\n // Sort by tier, then by input rate\n const tierOrder = { fast: 0, standard: 1, reasoning: 2, premium: 3 };\n const tierDiff = tierOrder[a.tier as keyof typeof tierOrder] - tierOrder[b.tier as keyof typeof tierOrder];\n if (tierDiff !== 0) return tierDiff;\n return a.inputRate - b.inputRate;\n });\n}\n\n/**\n * Check if a model exists in our pricing.\n */\nexport function isValidModel(model: string): boolean {\n return model in MODEL_PRICING;\n}\n\n/**\n * Format token pricing for display\n * Example: \"$0.15 per 1M tokens (gpt-4o-mini)\"\n */\nexport function formatTokenPricing(model: string = DEFAULT_MODEL): string {\n const pricing = MODEL_PRICING[model] ?? MODEL_PRICING[DEFAULT_MODEL];\n const actualModel = MODEL_PRICING[model] ? model : DEFAULT_MODEL;\n return `$${pricing.input.toFixed(2)} per 1M tokens (${actualModel})`;\n}\n\n"],"mappings":"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","names":[]}
|