@dexterai/x402 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/index.cjs +1 -954
- package/dist/adapters/index.js +1 -903
- package/dist/client/index.cjs +1 -1907
- package/dist/client/index.d.cts +2 -223
- package/dist/client/index.d.ts +2 -223
- package/dist/client/index.js +1 -1871
- package/dist/react/index.cjs +1 -1820
- package/dist/react/index.js +1 -1792
- package/dist/server/index.cjs +23 -1901
- package/dist/server/index.js +23 -1831
- package/dist/utils/index.cjs +1 -111
- package/dist/utils/index.js +1 -80
- package/package.json +3 -2
package/dist/server/index.js
CHANGED
|
@@ -1,675 +1,4 @@
|
|
|
1
|
-
// src/types.ts
|
|
2
|
-
var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
3
|
-
var BASE_MAINNET_NETWORK = "eip155:8453";
|
|
4
|
-
var USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
5
|
-
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
6
|
-
var DEXTER_FACILITATOR_URL = "https://x402.dexter.cash";
|
|
7
|
-
|
|
8
|
-
// src/utils.ts
|
|
9
|
-
function toAtomicUnits(amount, decimals) {
|
|
10
|
-
const multiplier = Math.pow(10, decimals);
|
|
11
|
-
return Math.floor(amount * multiplier).toString();
|
|
12
|
-
}
|
|
13
|
-
function isSolanaNetwork(network) {
|
|
14
|
-
return network.startsWith("solana:") || network === "solana";
|
|
15
|
-
}
|
|
16
|
-
function safeBase64Encode(str) {
|
|
17
|
-
if (typeof Buffer !== "undefined") {
|
|
18
|
-
return Buffer.from(str, "utf-8").toString("base64");
|
|
19
|
-
}
|
|
20
|
-
const bytes = new TextEncoder().encode(str);
|
|
21
|
-
let binary = "";
|
|
22
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
23
|
-
binary += String.fromCharCode(bytes[i]);
|
|
24
|
-
}
|
|
25
|
-
return btoa(binary);
|
|
26
|
-
}
|
|
27
|
-
function safeBase64Decode(encoded) {
|
|
28
|
-
if (typeof Buffer !== "undefined") {
|
|
29
|
-
return Buffer.from(encoded, "base64").toString("utf-8");
|
|
30
|
-
}
|
|
31
|
-
const binary = atob(encoded);
|
|
32
|
-
const bytes = new Uint8Array(binary.length);
|
|
33
|
-
for (let i = 0; i < binary.length; i++) {
|
|
34
|
-
bytes[i] = binary.charCodeAt(i);
|
|
35
|
-
}
|
|
36
|
-
return new TextDecoder().decode(bytes);
|
|
37
|
-
}
|
|
38
|
-
function encodeBase64Json(obj) {
|
|
39
|
-
return safeBase64Encode(JSON.stringify(obj));
|
|
40
|
-
}
|
|
41
|
-
function decodeBase64Json(encoded) {
|
|
42
|
-
return JSON.parse(safeBase64Decode(encoded));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/server/facilitator-client.ts
|
|
46
|
-
function isRetryable(error) {
|
|
47
|
-
if (error instanceof TypeError) return true;
|
|
48
|
-
if (error && typeof error === "object" && "status" in error) {
|
|
49
|
-
const status = error.status;
|
|
50
|
-
return status >= 500 && status < 600;
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
var HttpError = class extends Error {
|
|
55
|
-
status;
|
|
56
|
-
body;
|
|
57
|
-
constructor(status, body) {
|
|
58
|
-
super(`HTTP ${status}`);
|
|
59
|
-
this.status = status;
|
|
60
|
-
this.body = body;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
var FacilitatorClient = class {
|
|
64
|
-
facilitatorUrl;
|
|
65
|
-
cachedSupported = null;
|
|
66
|
-
cacheTime = 0;
|
|
67
|
-
CACHE_TTL_MS = 6e4;
|
|
68
|
-
timeoutMs;
|
|
69
|
-
maxRetries;
|
|
70
|
-
retryBaseMs;
|
|
71
|
-
constructor(facilitatorUrl = DEXTER_FACILITATOR_URL, config) {
|
|
72
|
-
this.facilitatorUrl = facilitatorUrl.replace(/\/$/, "");
|
|
73
|
-
this.timeoutMs = config?.timeoutMs ?? 1e4;
|
|
74
|
-
this.maxRetries = config?.maxRetries ?? 3;
|
|
75
|
-
this.retryBaseMs = config?.retryBaseMs ?? 500;
|
|
76
|
-
}
|
|
77
|
-
async fetchWithTimeout(url, init) {
|
|
78
|
-
const controller = new AbortController();
|
|
79
|
-
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
80
|
-
try {
|
|
81
|
-
return await fetch(url, { ...init, signal: controller.signal });
|
|
82
|
-
} finally {
|
|
83
|
-
clearTimeout(timer);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
async fetchWithRetry(url, init) {
|
|
87
|
-
let lastError;
|
|
88
|
-
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
89
|
-
try {
|
|
90
|
-
const response = await this.fetchWithTimeout(url, init);
|
|
91
|
-
if (!response.ok && response.status >= 500) {
|
|
92
|
-
throw new HttpError(response.status, await response.text());
|
|
93
|
-
}
|
|
94
|
-
return response;
|
|
95
|
-
} catch (error) {
|
|
96
|
-
lastError = error;
|
|
97
|
-
if (attempt < this.maxRetries - 1 && isRetryable(error)) {
|
|
98
|
-
const delay = this.retryBaseMs * Math.pow(2, attempt);
|
|
99
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
throw lastError;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Get supported payment kinds from the facilitator.
|
|
109
|
-
* Results are cached for 1 minute to reduce network calls.
|
|
110
|
-
*/
|
|
111
|
-
async getSupported() {
|
|
112
|
-
const now = Date.now();
|
|
113
|
-
if (this.cachedSupported && now - this.cacheTime < this.CACHE_TTL_MS) {
|
|
114
|
-
return this.cachedSupported;
|
|
115
|
-
}
|
|
116
|
-
const response = await this.fetchWithTimeout(`${this.facilitatorUrl}/supported`);
|
|
117
|
-
if (!response.ok) {
|
|
118
|
-
throw new Error(`Facilitator /supported returned ${response.status}`);
|
|
119
|
-
}
|
|
120
|
-
this.cachedSupported = await response.json();
|
|
121
|
-
this.cacheTime = now;
|
|
122
|
-
return this.cachedSupported;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Get the fee payer address for a specific network
|
|
126
|
-
*/
|
|
127
|
-
async getFeePayer(network) {
|
|
128
|
-
const supported = await this.getSupported();
|
|
129
|
-
const kind = supported.kinds.find(
|
|
130
|
-
(k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
|
|
131
|
-
);
|
|
132
|
-
if (!kind) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`Facilitator does not support network "${network}" with a recognized scheme`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
return kind.extra?.feePayer;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Get extra data for a network (feePayer, decimals, EIP-712 data, etc.)
|
|
141
|
-
*/
|
|
142
|
-
async getNetworkExtra(network) {
|
|
143
|
-
const supported = await this.getSupported();
|
|
144
|
-
const kind = supported.kinds.find(
|
|
145
|
-
(k) => k.x402Version === 2 && (k.scheme === "exact" || k.scheme === "exact-approval") && k.network === network
|
|
146
|
-
);
|
|
147
|
-
return kind?.extra;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Verify a payment with the facilitator.
|
|
151
|
-
* Retries on 5xx and network errors with exponential backoff.
|
|
152
|
-
*/
|
|
153
|
-
async verifyPayment(paymentSignatureHeader, requirements) {
|
|
154
|
-
try {
|
|
155
|
-
const paymentPayload = decodeBase64Json(paymentSignatureHeader);
|
|
156
|
-
const response = await this.fetchWithRetry(`${this.facilitatorUrl}/verify`, {
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers: { "Content-Type": "application/json" },
|
|
159
|
-
body: JSON.stringify({
|
|
160
|
-
x402Version: 2,
|
|
161
|
-
paymentPayload,
|
|
162
|
-
paymentRequirements: requirements
|
|
163
|
-
})
|
|
164
|
-
});
|
|
165
|
-
if (!response.ok) {
|
|
166
|
-
return {
|
|
167
|
-
isValid: false,
|
|
168
|
-
invalidReason: `facilitator_error_${response.status}`
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
return await response.json();
|
|
172
|
-
} catch (error) {
|
|
173
|
-
const reason = error instanceof HttpError ? `facilitator_error_${error.status}` : error instanceof Error && error.name === "AbortError" ? "facilitator_timeout" : error instanceof Error ? error.message : "unexpected_verify_error";
|
|
174
|
-
return { isValid: false, invalidReason: reason };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Settle a payment with the facilitator.
|
|
179
|
-
* Retries on 5xx and network errors with exponential backoff.
|
|
180
|
-
*/
|
|
181
|
-
async settlePayment(paymentSignatureHeader, requirements) {
|
|
182
|
-
try {
|
|
183
|
-
const paymentPayload = decodeBase64Json(paymentSignatureHeader);
|
|
184
|
-
const response = await this.fetchWithRetry(`${this.facilitatorUrl}/settle`, {
|
|
185
|
-
method: "POST",
|
|
186
|
-
headers: { "Content-Type": "application/json" },
|
|
187
|
-
body: JSON.stringify({
|
|
188
|
-
x402Version: 2,
|
|
189
|
-
paymentPayload,
|
|
190
|
-
paymentRequirements: requirements
|
|
191
|
-
})
|
|
192
|
-
});
|
|
193
|
-
if (!response.ok) {
|
|
194
|
-
return {
|
|
195
|
-
success: false,
|
|
196
|
-
network: requirements.network,
|
|
197
|
-
errorReason: `facilitator_error_${response.status}`
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
const result = await response.json();
|
|
201
|
-
return { ...result, network: requirements.network };
|
|
202
|
-
} catch (error) {
|
|
203
|
-
const reason = error instanceof HttpError ? `facilitator_error_${error.status}` : error instanceof Error && error.name === "AbortError" ? "facilitator_timeout" : error instanceof Error ? error.message : "unexpected_settle_error";
|
|
204
|
-
return {
|
|
205
|
-
success: false,
|
|
206
|
-
network: requirements.network,
|
|
207
|
-
errorReason: reason
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// src/server/x402-server.ts
|
|
214
|
-
function extractAmountFromHeader(paymentSignatureHeader) {
|
|
215
|
-
try {
|
|
216
|
-
const decoded = decodeBase64Json(paymentSignatureHeader);
|
|
217
|
-
return decoded?.accepted?.amount ?? decoded?.accepted?.maxAmountRequired;
|
|
218
|
-
} catch {
|
|
219
|
-
return void 0;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
function createX402Server(config) {
|
|
223
|
-
const {
|
|
224
|
-
payTo,
|
|
225
|
-
facilitatorUrl = DEXTER_FACILITATOR_URL,
|
|
226
|
-
network = SOLANA_MAINNET_NETWORK,
|
|
227
|
-
asset = { address: USDC_MINT, decimals: 6 },
|
|
228
|
-
defaultTimeoutSeconds = 60
|
|
229
|
-
} = config;
|
|
230
|
-
const facilitator = new FacilitatorClient(facilitatorUrl);
|
|
231
|
-
let cachedExtra = null;
|
|
232
|
-
const requirementsCache = /* @__PURE__ */ new Map();
|
|
233
|
-
const CACHE_PRUNE_INTERVAL = 3e4;
|
|
234
|
-
let lastPrune = Date.now();
|
|
235
|
-
function cacheRequirements(accept) {
|
|
236
|
-
const ttl = (accept.maxTimeoutSeconds || defaultTimeoutSeconds) * 1e3;
|
|
237
|
-
requirementsCache.set(accept.payTo, { accept, expiresAt: Date.now() + ttl });
|
|
238
|
-
if (Date.now() - lastPrune > CACHE_PRUNE_INTERVAL) {
|
|
239
|
-
const now = Date.now();
|
|
240
|
-
for (const [key, entry] of requirementsCache) {
|
|
241
|
-
if (entry.expiresAt < now) requirementsCache.delete(key);
|
|
242
|
-
}
|
|
243
|
-
lastPrune = now;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function getCachedRequirements(address) {
|
|
247
|
-
const entry = requirementsCache.get(address);
|
|
248
|
-
if (!entry) return void 0;
|
|
249
|
-
if (entry.expiresAt < Date.now()) {
|
|
250
|
-
requirementsCache.delete(address);
|
|
251
|
-
return void 0;
|
|
252
|
-
}
|
|
253
|
-
return entry.accept;
|
|
254
|
-
}
|
|
255
|
-
async function resolvePayTo(context) {
|
|
256
|
-
if (typeof payTo === "string") return payTo;
|
|
257
|
-
return payTo(context || {});
|
|
258
|
-
}
|
|
259
|
-
async function getNetworkExtra() {
|
|
260
|
-
if (!cachedExtra) {
|
|
261
|
-
cachedExtra = await facilitator.getNetworkExtra(network);
|
|
262
|
-
}
|
|
263
|
-
const isSvm = isSolanaNetwork(network);
|
|
264
|
-
if (isSvm && !cachedExtra?.feePayer) {
|
|
265
|
-
throw new Error(`Facilitator does not provide feePayer for network "${network}"`);
|
|
266
|
-
}
|
|
267
|
-
return {
|
|
268
|
-
...cachedExtra?.feePayer ? { feePayer: cachedExtra.feePayer } : {},
|
|
269
|
-
decimals: cachedExtra?.decimals ?? asset.decimals,
|
|
270
|
-
name: cachedExtra?.name,
|
|
271
|
-
version: cachedExtra?.version
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
async function buildPaymentAccept(resolvedPayTo, options) {
|
|
275
|
-
const {
|
|
276
|
-
amountAtomic,
|
|
277
|
-
timeoutSeconds = defaultTimeoutSeconds
|
|
278
|
-
} = options;
|
|
279
|
-
const extra = await getNetworkExtra();
|
|
280
|
-
const accept = {
|
|
281
|
-
scheme: "exact",
|
|
282
|
-
network,
|
|
283
|
-
amount: amountAtomic,
|
|
284
|
-
maxAmountRequired: amountAtomic,
|
|
285
|
-
asset: asset.address,
|
|
286
|
-
payTo: resolvedPayTo,
|
|
287
|
-
maxTimeoutSeconds: timeoutSeconds,
|
|
288
|
-
extra
|
|
289
|
-
};
|
|
290
|
-
cacheRequirements(accept);
|
|
291
|
-
return accept;
|
|
292
|
-
}
|
|
293
|
-
async function getPaymentAccept(options) {
|
|
294
|
-
const address = await resolvePayTo({
|
|
295
|
-
amountAtomic: options.amountAtomic,
|
|
296
|
-
resourceUrl: options.resourceUrl
|
|
297
|
-
});
|
|
298
|
-
return buildPaymentAccept(address, options);
|
|
299
|
-
}
|
|
300
|
-
async function buildRequirements(options) {
|
|
301
|
-
const {
|
|
302
|
-
resourceUrl,
|
|
303
|
-
description,
|
|
304
|
-
mimeType = "application/json"
|
|
305
|
-
} = options;
|
|
306
|
-
const resource = {
|
|
307
|
-
url: resourceUrl,
|
|
308
|
-
description,
|
|
309
|
-
mimeType
|
|
310
|
-
};
|
|
311
|
-
const accept = await getPaymentAccept(options);
|
|
312
|
-
return {
|
|
313
|
-
x402Version: 2,
|
|
314
|
-
resource,
|
|
315
|
-
accepts: [accept],
|
|
316
|
-
error: "Payment required"
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
function encodeRequirements(requirements) {
|
|
320
|
-
return encodeBase64Json(requirements);
|
|
321
|
-
}
|
|
322
|
-
function create402Response(requirements) {
|
|
323
|
-
return {
|
|
324
|
-
status: 402,
|
|
325
|
-
headers: {
|
|
326
|
-
"PAYMENT-REQUIRED": encodeRequirements(requirements)
|
|
327
|
-
},
|
|
328
|
-
body: {}
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
async function verifyPayment(paymentSignatureHeader, requirements) {
|
|
332
|
-
if (!requirements) {
|
|
333
|
-
const address = await resolvePayTo({ paymentHeader: paymentSignatureHeader });
|
|
334
|
-
requirements = getCachedRequirements(address);
|
|
335
|
-
if (!requirements) {
|
|
336
|
-
requirements = await buildPaymentAccept(address, {
|
|
337
|
-
amountAtomic: extractAmountFromHeader(paymentSignatureHeader) ?? "0",
|
|
338
|
-
resourceUrl: ""
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return facilitator.verifyPayment(paymentSignatureHeader, requirements);
|
|
343
|
-
}
|
|
344
|
-
async function settlePayment(paymentSignatureHeader, requirements) {
|
|
345
|
-
if (!requirements) {
|
|
346
|
-
const address = await resolvePayTo({ paymentHeader: paymentSignatureHeader });
|
|
347
|
-
requirements = getCachedRequirements(address);
|
|
348
|
-
if (!requirements) {
|
|
349
|
-
requirements = await buildPaymentAccept(address, {
|
|
350
|
-
amountAtomic: extractAmountFromHeader(paymentSignatureHeader) ?? "0",
|
|
351
|
-
resourceUrl: ""
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return facilitator.settlePayment(paymentSignatureHeader, requirements);
|
|
356
|
-
}
|
|
357
|
-
return {
|
|
358
|
-
buildRequirements,
|
|
359
|
-
encodeRequirements,
|
|
360
|
-
create402Response,
|
|
361
|
-
verifyPayment,
|
|
362
|
-
settlePayment,
|
|
363
|
-
getPaymentAccept,
|
|
364
|
-
network,
|
|
365
|
-
facilitator
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// src/server/stripe-payto.ts
|
|
370
|
-
var stripeProviderNetworks = /* @__PURE__ */ new WeakMap();
|
|
371
|
-
function getStripeProviderNetwork(provider) {
|
|
372
|
-
return stripeProviderNetworks.get(provider);
|
|
373
|
-
}
|
|
374
|
-
var STRIPE_NETWORK_KEYS = {
|
|
375
|
-
"base": "base",
|
|
376
|
-
"base-sepolia": "base_sepolia"
|
|
377
|
-
};
|
|
378
|
-
var CAIP2_NETWORKS = {
|
|
379
|
-
"base": "eip155:8453",
|
|
380
|
-
"base-sepolia": "eip155:84532"
|
|
381
|
-
};
|
|
382
|
-
var USDC_DECIMALS = 6;
|
|
383
|
-
function stripePayTo(secretKeyOrConfig) {
|
|
384
|
-
const config = typeof secretKeyOrConfig === "string" ? { secretKey: secretKeyOrConfig } : secretKeyOrConfig;
|
|
385
|
-
const networkName = config.network ?? "base";
|
|
386
|
-
const stripeNetworkKey = STRIPE_NETWORK_KEYS[networkName] ?? "base";
|
|
387
|
-
const caip2Network = CAIP2_NETWORKS[networkName] ?? "eip155:8453";
|
|
388
|
-
const apiVersion = config.apiVersion ?? "2026-01-28.clover";
|
|
389
|
-
let stripeClient = null;
|
|
390
|
-
async function getStripe() {
|
|
391
|
-
if (stripeClient) return stripeClient;
|
|
392
|
-
try {
|
|
393
|
-
const { default: Stripe } = await import("stripe");
|
|
394
|
-
stripeClient = new Stripe(config.secretKey, {
|
|
395
|
-
apiVersion,
|
|
396
|
-
appInfo: {
|
|
397
|
-
name: "@dexterai/x402",
|
|
398
|
-
url: "https://dexter.cash/sdk"
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
return stripeClient;
|
|
402
|
-
} catch {
|
|
403
|
-
throw new Error(
|
|
404
|
-
'The "stripe" package is required for stripePayTo(). Install it with: npm install stripe'
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
const provider = async (context) => {
|
|
409
|
-
if (context.paymentHeader) {
|
|
410
|
-
try {
|
|
411
|
-
const decoded = JSON.parse(
|
|
412
|
-
Buffer.from(context.paymentHeader, "base64").toString()
|
|
413
|
-
);
|
|
414
|
-
const toAddress = decoded.payload?.authorization?.to;
|
|
415
|
-
if (toAddress && typeof toAddress === "string") {
|
|
416
|
-
return toAddress;
|
|
417
|
-
}
|
|
418
|
-
const acceptedPayTo = decoded.accepted?.payTo;
|
|
419
|
-
if (acceptedPayTo && typeof acceptedPayTo === "string") {
|
|
420
|
-
return acceptedPayTo;
|
|
421
|
-
}
|
|
422
|
-
} catch {
|
|
423
|
-
}
|
|
424
|
-
throw new Error(
|
|
425
|
-
"Could not extract deposit address from payment header. Ensure the client is sending a valid x402 PAYMENT-SIGNATURE."
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
const stripe = await getStripe();
|
|
429
|
-
const amountAtomic = context.amountAtomic ? parseInt(context.amountAtomic, 10) : 1e4;
|
|
430
|
-
const amountInCents = Math.max(1, Math.round(amountAtomic / Math.pow(10, USDC_DECIMALS - 2)));
|
|
431
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
432
|
-
amount: amountInCents,
|
|
433
|
-
currency: "usd",
|
|
434
|
-
payment_method_types: ["crypto"],
|
|
435
|
-
payment_method_data: { type: "crypto" },
|
|
436
|
-
payment_method_options: {
|
|
437
|
-
crypto: { mode: "custom" }
|
|
438
|
-
},
|
|
439
|
-
confirm: true
|
|
440
|
-
});
|
|
441
|
-
const nextAction = paymentIntent.next_action;
|
|
442
|
-
if (!nextAction?.crypto_collect_deposit_details) {
|
|
443
|
-
throw new Error(
|
|
444
|
-
"Stripe PaymentIntent did not return crypto deposit details. Ensure your Stripe account has crypto payins enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto"
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
const depositDetails = nextAction.crypto_collect_deposit_details;
|
|
448
|
-
const payToAddress = depositDetails.deposit_addresses?.[stripeNetworkKey]?.address;
|
|
449
|
-
if (!payToAddress) {
|
|
450
|
-
throw new Error(
|
|
451
|
-
`No deposit address found for network "${stripeNetworkKey}". Available networks: ${Object.keys(depositDetails.deposit_addresses || {}).join(", ")}`
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
return payToAddress;
|
|
455
|
-
};
|
|
456
|
-
provider._x402Defaults = {
|
|
457
|
-
network: caip2Network,
|
|
458
|
-
facilitatorUrl: "https://x402.dexter.cash"
|
|
459
|
-
};
|
|
460
|
-
stripeProviderNetworks.set(provider, caip2Network);
|
|
461
|
-
return provider;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// src/server/middleware.ts
|
|
465
|
-
var DEFAULT_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
466
|
-
var USDC_DECIMALS2 = 6;
|
|
467
|
-
function resolvePayToForNetwork(payTo, network) {
|
|
468
|
-
if (typeof payTo === "string" || typeof payTo === "function") return payTo;
|
|
469
|
-
if (network in payTo) return payTo[network];
|
|
470
|
-
const prefix = network.split(":")[0];
|
|
471
|
-
const globKey = `${prefix}:*`;
|
|
472
|
-
if (globKey in payTo) return payTo[globKey];
|
|
473
|
-
if ("*" in payTo) return payTo["*"];
|
|
474
|
-
throw new Error(`No payTo configured for network "${network}"`);
|
|
475
|
-
}
|
|
476
|
-
function x402Middleware(config) {
|
|
477
|
-
const {
|
|
478
|
-
payTo,
|
|
479
|
-
amount,
|
|
480
|
-
asset,
|
|
481
|
-
description,
|
|
482
|
-
resourceUrl: staticResourceUrl,
|
|
483
|
-
mimeType,
|
|
484
|
-
timeoutSeconds,
|
|
485
|
-
verbose = false,
|
|
486
|
-
getResourceUrl,
|
|
487
|
-
getAmount,
|
|
488
|
-
getDescription
|
|
489
|
-
} = config;
|
|
490
|
-
const log = verbose ? console.log.bind(console, "[x402:middleware]") : () => {
|
|
491
|
-
};
|
|
492
|
-
const singleProviderDefaults = typeof payTo === "function" ? payTo._x402Defaults : void 0;
|
|
493
|
-
const facilitatorUrl = config.facilitatorUrl ?? singleProviderDefaults?.facilitatorUrl;
|
|
494
|
-
const configuredNetworks = (() => {
|
|
495
|
-
if (config.network) {
|
|
496
|
-
return Array.isArray(config.network) ? config.network : [config.network];
|
|
497
|
-
}
|
|
498
|
-
if (singleProviderDefaults?.network) return [singleProviderDefaults.network];
|
|
499
|
-
return [DEFAULT_NETWORK];
|
|
500
|
-
})();
|
|
501
|
-
const servers = /* @__PURE__ */ new Map();
|
|
502
|
-
for (const net of configuredNetworks) {
|
|
503
|
-
const netPayTo = resolvePayToForNetwork(payTo, net);
|
|
504
|
-
if (typeof netPayTo === "function") {
|
|
505
|
-
const stripeNet = getStripeProviderNetwork(netPayTo);
|
|
506
|
-
if (stripeNet && net !== stripeNet) {
|
|
507
|
-
throw new Error(
|
|
508
|
-
`stripePayTo is configured for "${stripeNet}" but middleware includes network "${net}". Stripe only supports Base deposit addresses. Use a static payTo for other chains.`
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
servers.set(net, createX402Server({
|
|
513
|
-
payTo: netPayTo,
|
|
514
|
-
network: net,
|
|
515
|
-
asset,
|
|
516
|
-
facilitatorUrl,
|
|
517
|
-
defaultTimeoutSeconds: timeoutSeconds
|
|
518
|
-
}));
|
|
519
|
-
}
|
|
520
|
-
const primaryServer = servers.get(configuredNetworks[0]);
|
|
521
|
-
return async (req, res, next) => {
|
|
522
|
-
try {
|
|
523
|
-
const paymentSignature = req.headers["payment-signature"];
|
|
524
|
-
if (!paymentSignature) {
|
|
525
|
-
log("No payment signature, returning 402");
|
|
526
|
-
const resourceUrl = getResourceUrl?.(req) ?? staticResourceUrl ?? `${req.protocol}://${req.get("host")}${req.originalUrl}`;
|
|
527
|
-
const requestAmount = getAmount?.(req) ?? amount;
|
|
528
|
-
const requestDescription = getDescription?.(req) ?? description;
|
|
529
|
-
const decimals = asset?.decimals ?? USDC_DECIMALS2;
|
|
530
|
-
const amountAtomic = toAtomicUnits(parseFloat(requestAmount), decimals);
|
|
531
|
-
const requirementsOptions = {
|
|
532
|
-
amountAtomic,
|
|
533
|
-
resourceUrl,
|
|
534
|
-
description: requestDescription,
|
|
535
|
-
mimeType,
|
|
536
|
-
timeoutSeconds
|
|
537
|
-
};
|
|
538
|
-
const allAccepts = [];
|
|
539
|
-
let requirements = null;
|
|
540
|
-
for (const [, srv] of servers) {
|
|
541
|
-
try {
|
|
542
|
-
const reqs = await srv.buildRequirements(requirementsOptions);
|
|
543
|
-
allAccepts.push(...reqs.accepts);
|
|
544
|
-
if (!requirements) requirements = reqs;
|
|
545
|
-
} catch (e) {
|
|
546
|
-
log("Failed to build requirements for a network:", e);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
if (!requirements || allAccepts.length === 0) {
|
|
550
|
-
res.status(500).json({ error: "Failed to build payment requirements" });
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
requirements = { ...requirements, accepts: allAccepts };
|
|
554
|
-
const encoded = primaryServer.encodeRequirements(requirements);
|
|
555
|
-
res.setHeader("PAYMENT-REQUIRED", encoded);
|
|
556
|
-
res.status(402).json({
|
|
557
|
-
error: "Payment required",
|
|
558
|
-
accepts: requirements.accepts,
|
|
559
|
-
resource: requirements.resource
|
|
560
|
-
});
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
log("Payment signature received, verifying...");
|
|
564
|
-
let targetServer = primaryServer;
|
|
565
|
-
try {
|
|
566
|
-
const decoded = JSON.parse(Buffer.from(paymentSignature, "base64").toString());
|
|
567
|
-
const paymentNetwork = decoded?.accepted?.network;
|
|
568
|
-
if (paymentNetwork && servers.has(paymentNetwork)) {
|
|
569
|
-
targetServer = servers.get(paymentNetwork);
|
|
570
|
-
}
|
|
571
|
-
} catch {
|
|
572
|
-
}
|
|
573
|
-
const verifyResult = await targetServer.verifyPayment(paymentSignature);
|
|
574
|
-
if (!verifyResult.isValid) {
|
|
575
|
-
log("Payment verification failed:", verifyResult.invalidReason);
|
|
576
|
-
if (config.onVerifyFailed) {
|
|
577
|
-
try {
|
|
578
|
-
await config.onVerifyFailed({
|
|
579
|
-
reason: verifyResult.invalidReason,
|
|
580
|
-
resourceUrl: `${req.protocol}://${req.get("host")}${req.originalUrl}`
|
|
581
|
-
});
|
|
582
|
-
} catch {
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
res.status(402).json({
|
|
586
|
-
error: "Payment verification failed",
|
|
587
|
-
reason: verifyResult.invalidReason
|
|
588
|
-
});
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
log("Payment verified, settling...");
|
|
592
|
-
const settleResult = await targetServer.settlePayment(paymentSignature);
|
|
593
|
-
if (!settleResult.success) {
|
|
594
|
-
log("Payment settlement failed:", settleResult.errorReason);
|
|
595
|
-
res.status(402).json({
|
|
596
|
-
error: "Payment settlement failed",
|
|
597
|
-
reason: settleResult.errorReason
|
|
598
|
-
});
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
log("Payment settled:", settleResult.transaction);
|
|
602
|
-
const settledNetwork = settleResult.network || configuredNetworks[0];
|
|
603
|
-
if (config.onSettlement) {
|
|
604
|
-
try {
|
|
605
|
-
await config.onSettlement({
|
|
606
|
-
transaction: settleResult.transaction,
|
|
607
|
-
network: settledNetwork,
|
|
608
|
-
payer: verifyResult.payer ?? "",
|
|
609
|
-
resourceUrl: `${req.protocol}://${req.get("host")}${req.originalUrl}`
|
|
610
|
-
});
|
|
611
|
-
} catch {
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
req.x402 = {
|
|
615
|
-
transaction: settleResult.transaction,
|
|
616
|
-
payer: verifyResult.payer ?? "",
|
|
617
|
-
network: settledNetwork
|
|
618
|
-
};
|
|
619
|
-
const paymentResponseData = {
|
|
620
|
-
success: true,
|
|
621
|
-
transaction: settleResult.transaction,
|
|
622
|
-
network: settledNetwork,
|
|
623
|
-
payer: verifyResult.payer ?? ""
|
|
624
|
-
};
|
|
625
|
-
if (settleResult.extensions) {
|
|
626
|
-
paymentResponseData.extensions = settleResult.extensions;
|
|
627
|
-
}
|
|
628
|
-
res.setHeader("PAYMENT-RESPONSE", encodeBase64Json(paymentResponseData));
|
|
629
|
-
if (config.sponsoredAccess && settleResult.extensions?.["sponsored-access"]) {
|
|
630
|
-
const extData = settleResult.extensions["sponsored-access"];
|
|
631
|
-
const recs = extData?.info?.recommendations ?? extData?.recommendations;
|
|
632
|
-
if (recs && recs.length > 0) {
|
|
633
|
-
log("Injecting sponsored-access recommendations into response");
|
|
634
|
-
if (typeof config.sponsoredAccess === "object" && config.sponsoredAccess.onMatch) {
|
|
635
|
-
try {
|
|
636
|
-
config.sponsoredAccess.onMatch(recs, {
|
|
637
|
-
transaction: settleResult.transaction,
|
|
638
|
-
network: settledNetwork,
|
|
639
|
-
payer: verifyResult.payer ?? ""
|
|
640
|
-
});
|
|
641
|
-
} catch {
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
const originalJson = res.json.bind(res);
|
|
645
|
-
res.json = function patchedJson(body) {
|
|
646
|
-
if (typeof config.sponsoredAccess === "object" && config.sponsoredAccess.inject) {
|
|
647
|
-
return originalJson(config.sponsoredAccess.inject(body, recs));
|
|
648
|
-
}
|
|
649
|
-
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
650
|
-
return originalJson({ _x402_sponsored: recs, ...body });
|
|
651
|
-
}
|
|
652
|
-
return originalJson(body);
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
next();
|
|
657
|
-
} catch (error) {
|
|
658
|
-
log("Middleware error:", error);
|
|
659
|
-
res.status(500).json({
|
|
660
|
-
error: "Payment processing error"
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// src/server/browser-support.ts
|
|
667
|
-
function escapeHtml(s) {
|
|
668
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
669
|
-
}
|
|
670
|
-
var USDC_ICON_SVG = `<svg width="18" height="18" viewBox="0 0 2000 2000" xmlns="http://www.w3.org/2000/svg"><path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/><path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/><path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`;
|
|
671
|
-
var DEXTER_CREST_SVG = `<svg width="36" height="36" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F2681A" d="m324.93,313.11c-115.5,0-231,0-350,0l350,0z"/><path fill="#FDFAF5" d="m230.43,50.62c1.1.85 2.19 1.7 3.32 2.57 6.02 4.8 11.77 9.88 17.46 15.07.92.84.92.84 1.86 1.69 1.82 1.69 3.59 3.42 5.35 5.16.61.56 1.22 1.13 1.84 1.71 5.66 5.76 6.18 10.43 6.13 18.3.02 1.16.04 2.32.06 3.52.06 3.83.06 7.65.07 11.48.02 2.68.05 5.35.08 8.03.05 5.6.09 11.21.1 16.81.02 7.15.09 14.31.17 21.46.06 5.53.1 11.05.13 16.58.02 2.64.04 5.27.07 7.91.18 17.58.12 32.82-11.24 47.32-7.35 7.27-16.54 12.06-25.42 17.22-1.97 1.16-3.94 2.33-5.91 3.49-7.16 4.24-14.34 8.44-21.53 12.62-4.8 2.79-9.59 5.6-14.38 8.42-1.25.73-2.5 1.47-3.79 2.23-2.32 1.36-4.64 2.73-6.96 4.1-27.47 16.09-27.47 16.09-42.16 12.93-8.06-2.28-14.94-5.82-22.16-10.02-1.17-.67-2.34-1.34-3.54-2.04-24.55-14.25-43.58-27.03-51.9-55.58-1.07-4.58-1.54-8.92-1.52-13.61.28-9.5.28-9.5-3.3-17.97-1.81-1.49-3.68-2.92-5.59-4.28-9.19-7.06-12.7-20.03-14.18-31.06-.54-5.77-.55-11.56-.6-17.35-.03-1.32-.07-2.63-.1-3.99-.01-1.26-.02-2.53-.03-3.83-.02-1.15-.03-2.29-.05-3.47.72-4.02 1.94-5.36 5.21-7.74 2.89-.53 2.89-.53 6.07-.46 1.71.02 1.71.02 3.46.05 1.19.04 2.37.08 3.59.12 1.2.02 2.41.04 3.65.06 2.97.05 5.93.13 8.9.23.14-1.35.29-2.7.43-4.08.63-5 1.78-9.74 3.14-14.58.22-.79.43-1.59.66-2.4.53-1.92 1.06-3.84 1.6-5.76-1.55-.45-1.55-.45-3.13-.9-9.52-3.52-17.1-10.95-21.37-20.1-3.81-9.26-3.87-20.34-.29-29.68 6.49-13.99 16.36-23.23 30.66-29.01 49.81-17.69 115.79 8.35 155.13 38.85z"/><path fill="#F2671A" d="m142.93,22.62c.86.19 1.73.39 2.62.59 36.12 8.21 68.79 24.98 95.38 50.75 1.02.98 2.03 1.97 3.08 2.98 10.84 10.66 10.84 10.66 11.05 14.62-2.06 3.55-5.44 4.18-9.17 5.3-.79.25-1.59.49-2.41.75-28.13 8.43-60.95 6.37-87.13-7.16-.86-.49-1.71-.97-2.6-1.48-7.37-4.05-12.59-3.36-20.59-1.54-22.76 4-48.47 1.53-68.69-9.74-4.88-3.88-8.23-8.29-10.21-14.22-.93-10.38-.67-18.44 5.83-26.83 19.57-23.38 55.99-20.36 82.83-14z"/><path fill="#F16619" d="m44.93,129.12c27.36-.03 54.72-.05 82.08-.06 12.7-.01 25.41-.01 38.11-.03 11.07-.01 22.14-.02 33.2-.02 5.86 0 11.73-.01 17.59-.01 5.51-.01 11.03-.01 16.54-.01 2.03 0 4.06 0 6.09-.01 2.76-.01 5.52 0 8.28 0 .81 0 1.63-.01 2.47-.01 5.51.02 5.51.02 6.81 1.32.22 3.43.22 3.43 0 7-2.75 2.75-3.42 2.66-7.15 2.82-1.41.07-1.41.07-2.85.14-1.47.05-1.47.05-2.98.11-1.49.07-1.49.07-3 .14-2.45.11-4.9.21-7.35.3-.2 1.3-.4 2.59-.6 3.93-2.57 16.08-5.93 29.89-18.89 40.86-10.35 7.28-21.87 8.49-34.17 7.71-13.11-2.33-22.52-9.19-30.33-19.83-4.49-7.64-4.8-17.05-5.83-25.67-4.24.39-8.47.77-12.83 1.17-.28 1.84-.28 1.84-.56 3.71-2.32 14.39-5.63 23.35-16.95 33.11-2.32 1.67-2.32 1.67-4.65 1.67 4 4.67 9.06 6.59 14.87 8.24 3.79 1.09 3.79 1.09 6.12 3.43-.65 5.31-.65 5.31-2.33 7-8.42-.27-15.13-2.29-22.17-7-1.09-1.21-2.17-2.43-3.25-3.65-2.72-2.81-4.45-3.84-8.36-4.16-1.67-.02-3.34-.02-5.01.01-1.77-.04-3.54-.09-5.3-.15-1.27-.04-1.27-.04-2.56-.08-9.26-.54-17.6-4.56-24.51-10.64-9.58-11.11-11.03-22.56-10.72-36.82.02-1.4.03-2.8.05-4.24.04-3.42.1-6.85.17-10.27z"/><path fill="#F26117" d="m172.68,203.08c7.27.09 13.23 1.97 18.87 6.65 2.88 3.07 3.86 5.12 4.25 9.32-.12 1.01-.24 2.02-.36 3.06-2.55.95-2.55.95-5.83 1.17-3.28-2.84-3.28-2.84-5.83-5.83-.36.58-.71 1.16-1.08 1.75-7.6 11.29-20.06 17.74-33.05 21.09-20.36 3.1-36.81-1.66-53.37-13.73-2.33-2.11-2.33-2.11-4.67-5.61.42-3.45.99-4.49 3.5-7 4.07.37 5.95 2.13 8.75 4.96 9.81 8.93 22.53 11.87 35.51 11.69 11.74-1.05 22.38-5.85 31.57-13.15 2.06-2.45 2.06-2.45 3.5-4.67-1.66.07-1.66.07-3.35.15-3.65-.15-3.65-.15-5.98-2.48.75-6.18 1.46-7.19 7.58-7.36z"/></g></svg>`;
|
|
672
|
-
var DEXTER_STYLES = `
|
|
1
|
+
var Y="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",oe="eip155:8453";var ee="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",fe="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",L="https://x402.dexter.cash";function V(e,t){let n=Math.pow(10,t);return Math.floor(e*n).toString()}function ae(e){return e.startsWith("solana:")||e==="solana"}function ge(e){if(typeof Buffer<"u")return Buffer.from(e,"utf-8").toString("base64");let t=new TextEncoder().encode(e),n="";for(let r=0;r<t.length;r++)n+=String.fromCharCode(t[r]);return btoa(n)}function ye(e){if(typeof Buffer<"u")return Buffer.from(e,"base64").toString("utf-8");let t=atob(e),n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=t.charCodeAt(r);return new TextDecoder().decode(n)}function F(e){return ge(JSON.stringify(e))}function j(e){return JSON.parse(ye(e))}function xe(e){if(e instanceof TypeError)return!0;if(e&&typeof e=="object"&&"status"in e){let t=e.status;return t>=500&&t<600}return!1}var G=class extends Error{status;body;constructor(t,n){super(`HTTP ${t}`),this.status=t,this.body=n}},H=class{facilitatorUrl;cachedSupported=null;cacheTime=0;CACHE_TTL_MS=6e4;timeoutMs;maxRetries;retryBaseMs;constructor(t=L,n){this.facilitatorUrl=t.replace(/\/$/,""),this.timeoutMs=n?.timeoutMs??1e4,this.maxRetries=n?.maxRetries??3,this.retryBaseMs=n?.retryBaseMs??500}async fetchWithTimeout(t,n){let r=new AbortController,i=setTimeout(()=>r.abort(),this.timeoutMs);try{return await fetch(t,{...n,signal:r.signal})}finally{clearTimeout(i)}}async fetchWithRetry(t,n){let r;for(let i=0;i<this.maxRetries;i++)try{let c=await this.fetchWithTimeout(t,n);if(!c.ok&&c.status>=500)throw new G(c.status,await c.text());return c}catch(c){if(r=c,i<this.maxRetries-1&&xe(c)){let d=this.retryBaseMs*Math.pow(2,i);await new Promise(g=>setTimeout(g,d));continue}throw c}throw r}async getSupported(){let t=Date.now();if(this.cachedSupported&&t-this.cacheTime<this.CACHE_TTL_MS)return this.cachedSupported;let n=await this.fetchWithTimeout(`${this.facilitatorUrl}/supported`);if(!n.ok)throw new Error(`Facilitator /supported returned ${n.status}`);return this.cachedSupported=await n.json(),this.cacheTime=t,this.cachedSupported}async getFeePayer(t){let r=(await this.getSupported()).kinds.find(i=>i.x402Version===2&&(i.scheme==="exact"||i.scheme==="exact-approval")&&i.network===t);if(!r)throw new Error(`Facilitator does not support network "${t}" with a recognized scheme`);return r.extra?.feePayer}async getNetworkExtra(t){return(await this.getSupported()).kinds.find(i=>i.x402Version===2&&(i.scheme==="exact"||i.scheme==="exact-approval")&&i.network===t)?.extra}async verifyPayment(t,n){try{let r=j(t),i=await this.fetchWithRetry(`${this.facilitatorUrl}/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({x402Version:2,paymentPayload:r,paymentRequirements:n})});return i.ok?await i.json():{isValid:!1,invalidReason:`facilitator_error_${i.status}`}}catch(r){return{isValid:!1,invalidReason:r instanceof G?`facilitator_error_${r.status}`:r instanceof Error&&r.name==="AbortError"?"facilitator_timeout":r instanceof Error?r.message:"unexpected_verify_error"}}}async settlePayment(t,n){try{let r=j(t),i=await this.fetchWithRetry(`${this.facilitatorUrl}/settle`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({x402Version:2,paymentPayload:r,paymentRequirements:n})});return i.ok?{...await i.json(),network:n.network}:{success:!1,network:n.network,errorReason:`facilitator_error_${i.status}`}}catch(r){let i=r instanceof G?`facilitator_error_${r.status}`:r instanceof Error&&r.name==="AbortError"?"facilitator_timeout":r instanceof Error?r.message:"unexpected_settle_error";return{success:!1,network:n.network,errorReason:i}}}};function ce(e){try{let t=j(e);return t?.accepted?.amount??t?.accepted?.maxAmountRequired}catch{return}}function K(e){let{payTo:t,facilitatorUrl:n=L,network:r=Y,asset:i={address:ee,decimals:6},defaultTimeoutSeconds:c=60}=e,d=new H(n),g=null,P=new Map,w=3e4,k=Date.now();function A(a){let p=(a.maxTimeoutSeconds||c)*1e3;if(P.set(a.payTo,{accept:a,expiresAt:Date.now()+p}),Date.now()-k>w){let s=Date.now();for(let[R,_]of P)_.expiresAt<s&&P.delete(R);k=s}}function o(a){let p=P.get(a);if(p){if(p.expiresAt<Date.now()){P.delete(a);return}return p.accept}}async function x(a){return typeof t=="string"?t:t(a||{})}async function f(){if(g||(g=await d.getNetworkExtra(r)),ae(r)&&!g?.feePayer)throw new Error(`Facilitator does not provide feePayer for network "${r}"`);return{...g?.feePayer?{feePayer:g.feePayer}:{},decimals:g?.decimals??i.decimals,name:g?.name,version:g?.version}}async function m(a,p){let{amountAtomic:s,timeoutSeconds:R=c}=p,_=await f(),E={scheme:"exact",network:r,amount:s,maxAmountRequired:s,asset:i.address,payTo:a,maxTimeoutSeconds:R,extra:_};return A(E),E}async function y(a){let p=await x({amountAtomic:a.amountAtomic,resourceUrl:a.resourceUrl});return m(p,a)}async function S(a){let{resourceUrl:p,description:s,mimeType:R="application/json"}=a,_={url:p,description:s,mimeType:R},E=await y(a);return{x402Version:2,resource:_,accepts:[E],error:"Payment required"}}function u(a){return F(a)}function h(a){return{status:402,headers:{"PAYMENT-REQUIRED":u(a)},body:{}}}async function l(a,p){if(!p){let s=await x({paymentHeader:a});p=o(s),p||(p=await m(s,{amountAtomic:ce(a)??"0",resourceUrl:""}))}return d.verifyPayment(a,p)}async function b(a,p){if(!p){let s=await x({paymentHeader:a});p=o(s),p||(p=await m(s,{amountAtomic:ce(a)??"0",resourceUrl:""}))}return d.settlePayment(a,p)}return{buildRequirements:S,encodeRequirements:u,create402Response:h,verifyPayment:l,settlePayment:b,getPaymentAccept:y,network:r,facilitator:d}}var pe=new WeakMap;function ue(e){return pe.get(e)}var he={base:"base","base-sepolia":"base_sepolia"},be={base:"eip155:8453","base-sepolia":"eip155:84532"},Pe=6;function we(e){let t=typeof e=="string"?{secretKey:e}:e,n=t.network??"base",r=he[n]??"base",i=be[n]??"eip155:8453",c=t.apiVersion??"2026-01-28.clover",d=null;async function g(){if(d)return d;try{let{default:w}=await import("stripe");return d=new w(t.secretKey,{apiVersion:c,appInfo:{name:"@dexterai/x402",url:"https://dexter.cash/sdk"}}),d}catch{throw new Error('The "stripe" package is required for stripePayTo(). Install it with: npm install stripe')}}let P=async w=>{if(w.paymentHeader){try{let S=JSON.parse(Buffer.from(w.paymentHeader,"base64").toString()),u=S.payload?.authorization?.to;if(u&&typeof u=="string")return u;let h=S.accepted?.payTo;if(h&&typeof h=="string")return h}catch{}throw new Error("Could not extract deposit address from payment header. Ensure the client is sending a valid x402 PAYMENT-SIGNATURE.")}let k=await g(),A=w.amountAtomic?parseInt(w.amountAtomic,10):1e4,o=Math.max(1,Math.round(A/Math.pow(10,Pe-2))),f=(await k.paymentIntents.create({amount:o,currency:"usd",payment_method_types:["crypto"],payment_method_data:{type:"crypto"},payment_method_options:{crypto:{mode:"custom"}},confirm:!0})).next_action;if(!f?.crypto_collect_deposit_details)throw new Error("Stripe PaymentIntent did not return crypto deposit details. Ensure your Stripe account has crypto payins enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto");let m=f.crypto_collect_deposit_details,y=m.deposit_addresses?.[r]?.address;if(!y)throw new Error(`No deposit address found for network "${r}". Available networks: ${Object.keys(m.deposit_addresses||{}).join(", ")}`);return y};return P._x402Defaults={network:i,facilitatorUrl:"https://x402.dexter.cash"},pe.set(P,i),P}var Te="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",Re=6;function ke(e,t){if(typeof e=="string"||typeof e=="function")return e;if(t in e)return e[t];let r=`${t.split(":")[0]}:*`;if(r in e)return e[r];if("*"in e)return e["*"];throw new Error(`No payTo configured for network "${t}"`)}function Ae(e){let{payTo:t,amount:n,asset:r,description:i,resourceUrl:c,mimeType:d,timeoutSeconds:g,verbose:P=!1,getResourceUrl:w,getAmount:k,getDescription:A}=e,o=P?console.log.bind(console,"[x402:middleware]"):()=>{},x=typeof t=="function"?t._x402Defaults:void 0,f=e.facilitatorUrl??x?.facilitatorUrl,m=e.network?Array.isArray(e.network)?e.network:[e.network]:x?.network?[x.network]:[Te],y=new Map;for(let u of m){let h=ke(t,u);if(typeof h=="function"){let l=ue(h);if(l&&u!==l)throw new Error(`stripePayTo is configured for "${l}" but middleware includes network "${u}". Stripe only supports Base deposit addresses. Use a static payTo for other chains.`)}y.set(u,K({payTo:h,network:u,asset:r,facilitatorUrl:f,defaultTimeoutSeconds:g}))}let S=y.get(m[0]);return async(u,h,l)=>{try{let b=u.headers["payment-signature"];if(!b){o("No payment signature, returning 402");let E=w?.(u)??c??`${u.protocol}://${u.get("host")}${u.originalUrl}`,M=k?.(u)??n,$=A?.(u)??i,T=r?.decimals??Re,O={amountAtomic:V(parseFloat(M),T),resourceUrl:E,description:$,mimeType:d,timeoutSeconds:g},q=[],C=null;for(let[,Z]of y)try{let J=await Z.buildRequirements(O);q.push(...J.accepts),C||(C=J)}catch(J){o("Failed to build requirements for a network:",J)}if(!C||q.length===0){h.status(500).json({error:"Failed to build payment requirements"});return}C={...C,accepts:q};let X=S.encodeRequirements(C);h.setHeader("PAYMENT-REQUIRED",X),h.status(402).json({error:"Payment required",accepts:C.accepts,resource:C.resource});return}o("Payment signature received, verifying...");let a=S;try{let M=JSON.parse(Buffer.from(b,"base64").toString())?.accepted?.network;M&&y.has(M)&&(a=y.get(M))}catch{}let p=await a.verifyPayment(b);if(!p.isValid){if(o("Payment verification failed:",p.invalidReason),e.onVerifyFailed)try{await e.onVerifyFailed({reason:p.invalidReason,resourceUrl:`${u.protocol}://${u.get("host")}${u.originalUrl}`})}catch{}h.status(402).json({error:"Payment verification failed",reason:p.invalidReason});return}o("Payment verified, settling...");let s=await a.settlePayment(b);if(!s.success){o("Payment settlement failed:",s.errorReason),h.status(402).json({error:"Payment settlement failed",reason:s.errorReason});return}o("Payment settled:",s.transaction);let R=s.network||m[0];if(e.onSettlement)try{await e.onSettlement({transaction:s.transaction,network:R,payer:p.payer??"",resourceUrl:`${u.protocol}://${u.get("host")}${u.originalUrl}`})}catch{}u.x402={transaction:s.transaction,payer:p.payer??"",network:R};let _={success:!0,transaction:s.transaction,network:R,payer:p.payer??""};if(s.extensions&&(_.extensions=s.extensions),h.setHeader("PAYMENT-RESPONSE",F(_)),e.sponsoredAccess&&s.extensions?.["sponsored-access"]){let E=s.extensions["sponsored-access"],M=E?.info?.recommendations??E?.recommendations;if(M&&M.length>0){if(o("Injecting sponsored-access recommendations into response"),typeof e.sponsoredAccess=="object"&&e.sponsoredAccess.onMatch)try{e.sponsoredAccess.onMatch(M,{transaction:s.transaction,network:R,payer:p.payer??""})}catch{}let $=h.json.bind(h);h.json=function(v){return typeof e.sponsoredAccess=="object"&&e.sponsoredAccess.inject?$(e.sponsoredAccess.inject(v,M)):v&&typeof v=="object"&&!Array.isArray(v)?$({_x402_sponsored:M,...v}):$(v)}}}l()}catch(b){o("Middleware error:",b),h.status(500).json({error:"Payment processing error"})}}}function N(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}var Se='<svg width="18" height="18" viewBox="0 0 2000 2000" xmlns="http://www.w3.org/2000/svg"><path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/><path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/><path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>',Me='<svg width="36" height="36" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F2681A" d="m324.93,313.11c-115.5,0-231,0-350,0l350,0z"/><path fill="#FDFAF5" d="m230.43,50.62c1.1.85 2.19 1.7 3.32 2.57 6.02 4.8 11.77 9.88 17.46 15.07.92.84.92.84 1.86 1.69 1.82 1.69 3.59 3.42 5.35 5.16.61.56 1.22 1.13 1.84 1.71 5.66 5.76 6.18 10.43 6.13 18.3.02 1.16.04 2.32.06 3.52.06 3.83.06 7.65.07 11.48.02 2.68.05 5.35.08 8.03.05 5.6.09 11.21.1 16.81.02 7.15.09 14.31.17 21.46.06 5.53.1 11.05.13 16.58.02 2.64.04 5.27.07 7.91.18 17.58.12 32.82-11.24 47.32-7.35 7.27-16.54 12.06-25.42 17.22-1.97 1.16-3.94 2.33-5.91 3.49-7.16 4.24-14.34 8.44-21.53 12.62-4.8 2.79-9.59 5.6-14.38 8.42-1.25.73-2.5 1.47-3.79 2.23-2.32 1.36-4.64 2.73-6.96 4.1-27.47 16.09-27.47 16.09-42.16 12.93-8.06-2.28-14.94-5.82-22.16-10.02-1.17-.67-2.34-1.34-3.54-2.04-24.55-14.25-43.58-27.03-51.9-55.58-1.07-4.58-1.54-8.92-1.52-13.61.28-9.5.28-9.5-3.3-17.97-1.81-1.49-3.68-2.92-5.59-4.28-9.19-7.06-12.7-20.03-14.18-31.06-.54-5.77-.55-11.56-.6-17.35-.03-1.32-.07-2.63-.1-3.99-.01-1.26-.02-2.53-.03-3.83-.02-1.15-.03-2.29-.05-3.47.72-4.02 1.94-5.36 5.21-7.74 2.89-.53 2.89-.53 6.07-.46 1.71.02 1.71.02 3.46.05 1.19.04 2.37.08 3.59.12 1.2.02 2.41.04 3.65.06 2.97.05 5.93.13 8.9.23.14-1.35.29-2.7.43-4.08.63-5 1.78-9.74 3.14-14.58.22-.79.43-1.59.66-2.4.53-1.92 1.06-3.84 1.6-5.76-1.55-.45-1.55-.45-3.13-.9-9.52-3.52-17.1-10.95-21.37-20.1-3.81-9.26-3.87-20.34-.29-29.68 6.49-13.99 16.36-23.23 30.66-29.01 49.81-17.69 115.79 8.35 155.13 38.85z"/><path fill="#F2671A" d="m142.93,22.62c.86.19 1.73.39 2.62.59 36.12 8.21 68.79 24.98 95.38 50.75 1.02.98 2.03 1.97 3.08 2.98 10.84 10.66 10.84 10.66 11.05 14.62-2.06 3.55-5.44 4.18-9.17 5.3-.79.25-1.59.49-2.41.75-28.13 8.43-60.95 6.37-87.13-7.16-.86-.49-1.71-.97-2.6-1.48-7.37-4.05-12.59-3.36-20.59-1.54-22.76 4-48.47 1.53-68.69-9.74-4.88-3.88-8.23-8.29-10.21-14.22-.93-10.38-.67-18.44 5.83-26.83 19.57-23.38 55.99-20.36 82.83-14z"/><path fill="#F16619" d="m44.93,129.12c27.36-.03 54.72-.05 82.08-.06 12.7-.01 25.41-.01 38.11-.03 11.07-.01 22.14-.02 33.2-.02 5.86 0 11.73-.01 17.59-.01 5.51-.01 11.03-.01 16.54-.01 2.03 0 4.06 0 6.09-.01 2.76-.01 5.52 0 8.28 0 .81 0 1.63-.01 2.47-.01 5.51.02 5.51.02 6.81 1.32.22 3.43.22 3.43 0 7-2.75 2.75-3.42 2.66-7.15 2.82-1.41.07-1.41.07-2.85.14-1.47.05-1.47.05-2.98.11-1.49.07-1.49.07-3 .14-2.45.11-4.9.21-7.35.3-.2 1.3-.4 2.59-.6 3.93-2.57 16.08-5.93 29.89-18.89 40.86-10.35 7.28-21.87 8.49-34.17 7.71-13.11-2.33-22.52-9.19-30.33-19.83-4.49-7.64-4.8-17.05-5.83-25.67-4.24.39-8.47.77-12.83 1.17-.28 1.84-.28 1.84-.56 3.71-2.32 14.39-5.63 23.35-16.95 33.11-2.32 1.67-2.32 1.67-4.65 1.67 4 4.67 9.06 6.59 14.87 8.24 3.79 1.09 3.79 1.09 6.12 3.43-.65 5.31-.65 5.31-2.33 7-8.42-.27-15.13-2.29-22.17-7-1.09-1.21-2.17-2.43-3.25-3.65-2.72-2.81-4.45-3.84-8.36-4.16-1.67-.02-3.34-.02-5.01.01-1.77-.04-3.54-.09-5.3-.15-1.27-.04-1.27-.04-2.56-.08-9.26-.54-17.6-4.56-24.51-10.64-9.58-11.11-11.03-22.56-10.72-36.82.02-1.4.03-2.8.05-4.24.04-3.42.1-6.85.17-10.27z"/><path fill="#F26117" d="m172.68,203.08c7.27.09 13.23 1.97 18.87 6.65 2.88 3.07 3.86 5.12 4.25 9.32-.12 1.01-.24 2.02-.36 3.06-2.55.95-2.55.95-5.83 1.17-3.28-2.84-3.28-2.84-5.83-5.83-.36.58-.71 1.16-1.08 1.75-7.6 11.29-20.06 17.74-33.05 21.09-20.36 3.1-36.81-1.66-53.37-13.73-2.33-2.11-2.33-2.11-4.67-5.61.42-3.45.99-4.49 3.5-7 4.07.37 5.95 2.13 8.75 4.96 9.81 8.93 22.53 11.87 35.51 11.69 11.74-1.05 22.38-5.85 31.57-13.15 2.06-2.45 2.06-2.45 3.5-4.67-1.66.07-1.66.07-3.35.15-3.65-.15-3.65-.15-5.98-2.48.75-6.18 1.46-7.19 7.58-7.36z"/></g></svg>',ve=`
|
|
673
2
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap');
|
|
674
3
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
675
4
|
body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
|
|
@@ -691,8 +20,7 @@ h1{font-family:'Orbitron',sans-serif;font-size:1.15rem;font-weight:700;color:#f1
|
|
|
691
20
|
.footer a{color:#525252;text-decoration:none}
|
|
692
21
|
.footer a:hover{color:#737373}
|
|
693
22
|
.sep{width:3px;height:3px;border-radius:50%;background:#333}
|
|
694
|
-
|
|
695
|
-
var PAY_BUTTON_STYLES = `
|
|
23
|
+
`,Ee=`
|
|
696
24
|
.pay-section{margin:1.25rem 0}
|
|
697
25
|
.pay-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;background:linear-gradient(135deg,#F26B1A,#D13F00);color:#fff;border:none;padding:.65rem 2rem;border-radius:6px;font-family:'Inter',sans-serif;font-size:.95rem;font-weight:600;cursor:pointer;transition:opacity .15s,transform .1s;min-width:180px}
|
|
698
26
|
.pay-btn:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}
|
|
@@ -708,8 +36,7 @@ var PAY_BUTTON_STYLES = `
|
|
|
708
36
|
.result-box{background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.15);border-radius:6px;padding:.75rem;margin-top:.75rem;text-align:left;font-size:.78rem;max-height:200px;overflow:auto}
|
|
709
37
|
.result-box pre{color:#94a3b8;font-family:'SF Mono',Monaco,Consolas,monospace;white-space:pre-wrap;word-break:break-all}
|
|
710
38
|
.no-wallet{font-size:.82rem;color:#737373;margin:1rem 0}
|
|
711
|
-
|
|
712
|
-
var PAY_SCRIPT = `
|
|
39
|
+
`,_e=`
|
|
713
40
|
<script type="module">
|
|
714
41
|
// Payment data is embedded in #x402-data attributes
|
|
715
42
|
const dataEl = document.getElementById('x402-data');
|
|
@@ -906,48 +233,27 @@ if (btn) {
|
|
|
906
233
|
});
|
|
907
234
|
}
|
|
908
235
|
</script>
|
|
909
|
-
`;
|
|
910
|
-
function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config, rpcUrl, requestBody) {
|
|
911
|
-
let price = "?";
|
|
912
|
-
let description = "This resource requires payment";
|
|
913
|
-
let network = "";
|
|
914
|
-
try {
|
|
915
|
-
const decoded = JSON.parse(Buffer.from(paymentRequiredHeader, "base64").toString());
|
|
916
|
-
const accept = decoded.accepts?.[0];
|
|
917
|
-
if (accept) {
|
|
918
|
-
const amount = accept.amount ?? accept.maxAmountRequired ?? "0";
|
|
919
|
-
const decimals = accept.extra?.decimals || 6;
|
|
920
|
-
price = (Number(amount) / Math.pow(10, decimals)).toFixed(decimals > 4 ? 4 : 2);
|
|
921
|
-
network = accept.network || "";
|
|
922
|
-
}
|
|
923
|
-
if (decoded.resource?.description) {
|
|
924
|
-
description = decoded.resource.description;
|
|
925
|
-
}
|
|
926
|
-
} catch {
|
|
927
|
-
}
|
|
928
|
-
const chainName = network.includes("solana") ? "Solana" : network.includes("eip155") ? "Base" : "";
|
|
929
|
-
const endpointSection = config.showEndpoint ? `<div class="endpoint"><code>${escapeHtml(method)} ${escapeHtml(requestUrl)}</code></div>` : "";
|
|
930
|
-
return `<!DOCTYPE html>
|
|
236
|
+
`;function Ce(e,t,n,r,i,c){let d="?",g="This resource requires payment",P="";try{let A=JSON.parse(Buffer.from(e,"base64").toString()),o=A.accepts?.[0];if(o){let x=o.amount??o.maxAmountRequired??"0",f=o.extra?.decimals||6;d=(Number(x)/Math.pow(10,f)).toFixed(f>4?4:2),P=o.network||""}A.resource?.description&&(g=A.resource.description)}catch{}let w=P.includes("solana")?"Solana":P.includes("eip155")?"Base":"",k=r.showEndpoint?`<div class="endpoint"><code>${N(n)} ${N(t)}</code></div>`:"";return`<!DOCTYPE html>
|
|
931
237
|
<html lang="en">
|
|
932
238
|
<head>
|
|
933
239
|
<meta charset="utf-8">
|
|
934
240
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
935
|
-
<title>${
|
|
936
|
-
<style>${
|
|
241
|
+
<title>${N(r.title)} \u2014 ${N(d)} USDC</title>
|
|
242
|
+
<style>${ve}${Ee}</style>
|
|
937
243
|
</head>
|
|
938
244
|
<body>
|
|
939
245
|
<div class="card">
|
|
940
|
-
<div class="crest">${
|
|
941
|
-
<h1>${
|
|
942
|
-
<p class="desc">${
|
|
943
|
-
<div class="price">${
|
|
944
|
-
<div class="chain">${
|
|
945
|
-
${
|
|
246
|
+
<div class="crest">${Me}</div>
|
|
247
|
+
<h1>${N(r.title)}</h1>
|
|
248
|
+
<p class="desc">${N(g)}</p>
|
|
249
|
+
<div class="price">${Se}<span id="price-value">${N(d)}</span></div>
|
|
250
|
+
<div class="chain">${N(w)}${w?" network":""}</div>
|
|
251
|
+
${k}
|
|
946
252
|
|
|
947
253
|
<div id="pay-section" class="pay-section" style="display:none">
|
|
948
|
-
<button id="pay-btn" class="pay-btn">Pay ${
|
|
254
|
+
<button id="pay-btn" class="pay-btn">Pay ${d}</button>
|
|
949
255
|
<div id="pay-status" class="pay-status"></div>
|
|
950
|
-
<div class="pay-alt">or use <a href="${
|
|
256
|
+
<div class="pay-alt">or use <a href="${r.sdkUrl}">x402 SDK</a> for programmatic access</div>
|
|
951
257
|
</div>
|
|
952
258
|
|
|
953
259
|
<div id="no-wallet" class="no-wallet" style="display:none">
|
|
@@ -955,7 +261,7 @@ function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config,
|
|
|
955
261
|
<strong>Access this endpoint:</strong><br><br>
|
|
956
262
|
Use any x402-compatible client or a browser with a Solana wallet extension (Phantom, Solflare, Backpack).<br><br>
|
|
957
263
|
<code>npm install @dexterai/x402</code><br><br>
|
|
958
|
-
<a href="${
|
|
264
|
+
<a href="${r.sdkUrl}">x402 SDK docs →</a>
|
|
959
265
|
</div>
|
|
960
266
|
</div>
|
|
961
267
|
|
|
@@ -967,1128 +273,14 @@ function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config,
|
|
|
967
273
|
</div>
|
|
968
274
|
|
|
969
275
|
<div id="x402-data" style="display:none"
|
|
970
|
-
data-requirements="${
|
|
971
|
-
data-method="${
|
|
972
|
-
data-url="${
|
|
973
|
-
data-rpc="${
|
|
974
|
-
data-body="${
|
|
276
|
+
data-requirements="${e}"
|
|
277
|
+
data-method="${n}"
|
|
278
|
+
data-url="${t}"
|
|
279
|
+
data-rpc="${i}"
|
|
280
|
+
data-body="${c?Buffer.from(c).toString("base64"):""}"
|
|
975
281
|
></div>
|
|
976
|
-
${
|
|
282
|
+
${_e}
|
|
977
283
|
</body>
|
|
978
|
-
</html>`;
|
|
979
|
-
}
|
|
980
|
-
function x402BrowserSupport(config = {}) {
|
|
981
|
-
const resolvedConfig = {
|
|
982
|
-
title: config.title ?? "Payment Required",
|
|
983
|
-
branding: config.branding ?? 'Powered by <a href="https://docs.dexter.cash/docs/sdk/">Dexter x402</a>',
|
|
984
|
-
sdkUrl: config.sdkUrl ?? "https://docs.dexter.cash/docs/sdk/",
|
|
985
|
-
showEndpoint: config.showEndpoint ?? true
|
|
986
|
-
};
|
|
987
|
-
const rpcUrl = config.rpcUrl ?? "https://api.dexter.cash/api/solana/rpc";
|
|
988
|
-
return (req, res, next) => {
|
|
989
|
-
const originalJson = res.json.bind(res);
|
|
990
|
-
res.json = function(body) {
|
|
991
|
-
if (res.statusCode === 402 && req.accepts("html") && !req.headers["payment-signature"]) {
|
|
992
|
-
const paymentRequired = res.getHeader("PAYMENT-REQUIRED") || res.getHeader("payment-required");
|
|
993
|
-
if (paymentRequired && typeof paymentRequired === "string") {
|
|
994
|
-
let bodyStr;
|
|
995
|
-
if (req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) {
|
|
996
|
-
try {
|
|
997
|
-
bodyStr = JSON.stringify(req.body);
|
|
998
|
-
} catch {
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
const html = generatePaywallHtml(
|
|
1002
|
-
paymentRequired,
|
|
1003
|
-
req.originalUrl,
|
|
1004
|
-
req.method,
|
|
1005
|
-
resolvedConfig,
|
|
1006
|
-
rpcUrl,
|
|
1007
|
-
bodyStr
|
|
1008
|
-
);
|
|
1009
|
-
res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src *; img-src data:; frame-ancestors 'none'");
|
|
1010
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
1011
|
-
res.status(402).type("html").send(html);
|
|
1012
|
-
return res;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
return originalJson(body);
|
|
1016
|
-
};
|
|
1017
|
-
next();
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// src/server/access-pass.ts
|
|
1022
|
-
import crypto from "crypto";
|
|
1023
|
-
var DURATION_REGEX = /^(\d+)(m|h|d|w)$/;
|
|
1024
|
-
function parseTierDuration(tierId) {
|
|
1025
|
-
const match = tierId.match(DURATION_REGEX);
|
|
1026
|
-
if (!match) return null;
|
|
1027
|
-
const value = parseInt(match[1], 10);
|
|
1028
|
-
const unit = match[2];
|
|
1029
|
-
switch (unit) {
|
|
1030
|
-
case "m":
|
|
1031
|
-
return value * 60;
|
|
1032
|
-
case "h":
|
|
1033
|
-
return value * 3600;
|
|
1034
|
-
case "d":
|
|
1035
|
-
return value * 86400;
|
|
1036
|
-
case "w":
|
|
1037
|
-
return value * 604800;
|
|
1038
|
-
default:
|
|
1039
|
-
return null;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
function formatDuration(seconds) {
|
|
1043
|
-
if (seconds >= 604800 && seconds % 604800 === 0) return `${seconds / 604800} week${seconds / 604800 > 1 ? "s" : ""}`;
|
|
1044
|
-
if (seconds >= 86400 && seconds % 86400 === 0) return `${seconds / 86400} day${seconds / 86400 > 1 ? "s" : ""}`;
|
|
1045
|
-
if (seconds >= 3600 && seconds % 3600 === 0) return `${seconds / 3600} hour${seconds / 3600 > 1 ? "s" : ""}`;
|
|
1046
|
-
if (seconds >= 60 && seconds % 60 === 0) return `${seconds / 60} minute${seconds / 60 > 1 ? "s" : ""}`;
|
|
1047
|
-
return `${seconds} second${seconds > 1 ? "s" : ""}`;
|
|
1048
|
-
}
|
|
1049
|
-
function signJwt(payload, secret) {
|
|
1050
|
-
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
|
|
1051
|
-
const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
1052
|
-
const sig = crypto.createHmac("sha256", secret).update(`${header}.${body}`).digest("base64url");
|
|
1053
|
-
return `${header}.${body}.${sig}`;
|
|
1054
|
-
}
|
|
1055
|
-
function verifyJwt(token, secret) {
|
|
1056
|
-
try {
|
|
1057
|
-
const parts = token.split(".");
|
|
1058
|
-
if (parts.length !== 3) return null;
|
|
1059
|
-
const [header, body, sig] = parts;
|
|
1060
|
-
const expected = crypto.createHmac("sha256", secret).update(`${header}.${body}`).digest("base64url");
|
|
1061
|
-
if (sig !== expected) return null;
|
|
1062
|
-
const payload = JSON.parse(Buffer.from(body, "base64url").toString());
|
|
1063
|
-
if (payload.exp && Date.now() / 1e3 > payload.exp) return null;
|
|
1064
|
-
if (payload.sub !== "x402-access-pass") return null;
|
|
1065
|
-
return payload;
|
|
1066
|
-
} catch {
|
|
1067
|
-
return null;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
var DEFAULT_NETWORK2 = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
1071
|
-
var USDC_DECIMALS3 = 6;
|
|
1072
|
-
function x402AccessPass(config) {
|
|
1073
|
-
const {
|
|
1074
|
-
payTo,
|
|
1075
|
-
network = DEFAULT_NETWORK2,
|
|
1076
|
-
asset,
|
|
1077
|
-
facilitatorUrl,
|
|
1078
|
-
tiers: tierPrices,
|
|
1079
|
-
ratePerHour,
|
|
1080
|
-
secret: explicitSecret,
|
|
1081
|
-
issuer = "x402-access-pass",
|
|
1082
|
-
verbose = false,
|
|
1083
|
-
description
|
|
1084
|
-
} = config;
|
|
1085
|
-
const secret = explicitSecret ?? crypto.randomBytes(32);
|
|
1086
|
-
if (!explicitSecret) {
|
|
1087
|
-
console.warn("[x402:access-pass] No secret provided \u2014 access passes will be invalidated on server restart. Set `secret` for production use.");
|
|
1088
|
-
}
|
|
1089
|
-
if (!tierPrices && !ratePerHour) {
|
|
1090
|
-
throw new Error("x402AccessPass: at least one of `tiers` or `ratePerHour` is required");
|
|
1091
|
-
}
|
|
1092
|
-
const log = verbose ? console.log.bind(console, "[x402:access-pass]") : () => {
|
|
1093
|
-
};
|
|
1094
|
-
const decimals = asset?.decimals ?? USDC_DECIMALS3;
|
|
1095
|
-
const builtTiers = [];
|
|
1096
|
-
if (tierPrices) {
|
|
1097
|
-
for (const [id, price] of Object.entries(tierPrices)) {
|
|
1098
|
-
const seconds = parseTierDuration(id);
|
|
1099
|
-
if (!seconds) {
|
|
1100
|
-
console.warn(`x402AccessPass: skipping tier "${id}" \u2014 unrecognized duration format (use 5m, 1h, 24h, 7d)`);
|
|
1101
|
-
continue;
|
|
1102
|
-
}
|
|
1103
|
-
builtTiers.push({
|
|
1104
|
-
id,
|
|
1105
|
-
label: formatDuration(seconds),
|
|
1106
|
-
seconds,
|
|
1107
|
-
price,
|
|
1108
|
-
priceAtomic: toAtomicUnits(parseFloat(price), decimals)
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
|
-
builtTiers.sort((a, b) => a.seconds - b.seconds);
|
|
1112
|
-
}
|
|
1113
|
-
const server = createX402Server({
|
|
1114
|
-
payTo,
|
|
1115
|
-
network,
|
|
1116
|
-
asset,
|
|
1117
|
-
facilitatorUrl
|
|
1118
|
-
});
|
|
1119
|
-
const passInfo = {
|
|
1120
|
-
tiers: builtTiers.length > 0 ? builtTiers : void 0,
|
|
1121
|
-
ratePerHour: ratePerHour || void 0,
|
|
1122
|
-
issuer
|
|
1123
|
-
};
|
|
1124
|
-
const passInfoEncoded = encodeBase64Json(passInfo);
|
|
1125
|
-
function calculateCustomPrice(durationSeconds) {
|
|
1126
|
-
if (!ratePerHour) {
|
|
1127
|
-
throw new Error("Custom durations not supported \u2014 no ratePerHour configured");
|
|
1128
|
-
}
|
|
1129
|
-
const hours = durationSeconds / 3600;
|
|
1130
|
-
const price = (parseFloat(ratePerHour) * hours).toFixed(decimals > 4 ? 4 : 2);
|
|
1131
|
-
return { price, priceAtomic: toAtomicUnits(parseFloat(price), decimals) };
|
|
1132
|
-
}
|
|
1133
|
-
function resolvePricing(req) {
|
|
1134
|
-
const tierParam = req.query.tier;
|
|
1135
|
-
const durationParam = req.query.duration;
|
|
1136
|
-
if (tierParam) {
|
|
1137
|
-
const found = builtTiers.find((t) => t.id === tierParam);
|
|
1138
|
-
if (found) {
|
|
1139
|
-
return { tier: found.id, seconds: found.seconds, price: found.price, priceAtomic: found.priceAtomic, label: found.label };
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
if (durationParam) {
|
|
1143
|
-
const seconds = parseInt(durationParam, 10);
|
|
1144
|
-
if (seconds > 0 && ratePerHour) {
|
|
1145
|
-
const pricing2 = calculateCustomPrice(seconds);
|
|
1146
|
-
return { tier: "custom", seconds, ...pricing2, label: formatDuration(seconds) };
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
if (builtTiers.length > 0) {
|
|
1150
|
-
const t = builtTiers[0];
|
|
1151
|
-
return { tier: t.id, seconds: t.seconds, price: t.price, priceAtomic: t.priceAtomic, label: t.label };
|
|
1152
|
-
}
|
|
1153
|
-
const pricing = calculateCustomPrice(3600);
|
|
1154
|
-
return { tier: "custom", seconds: 3600, ...pricing, label: "1 hour" };
|
|
1155
|
-
}
|
|
1156
|
-
return async (req, res, next) => {
|
|
1157
|
-
try {
|
|
1158
|
-
const auth = req.headers.authorization;
|
|
1159
|
-
if (auth?.startsWith("Bearer ")) {
|
|
1160
|
-
const claims = verifyJwt(auth.slice(7), secret);
|
|
1161
|
-
if (claims) {
|
|
1162
|
-
log("Valid access pass:", claims.tier, "| expires:", new Date(claims.exp * 1e3).toISOString());
|
|
1163
|
-
req.accessPass = {
|
|
1164
|
-
tier: claims.tier,
|
|
1165
|
-
duration: claims.duration,
|
|
1166
|
-
expiresAt: new Date(claims.exp * 1e3).toISOString(),
|
|
1167
|
-
payer: claims.payer,
|
|
1168
|
-
network: claims.network
|
|
1169
|
-
};
|
|
1170
|
-
return next();
|
|
1171
|
-
}
|
|
1172
|
-
log("Invalid or expired access pass token");
|
|
1173
|
-
}
|
|
1174
|
-
const paymentSignature = req.headers["payment-signature"];
|
|
1175
|
-
if (paymentSignature) {
|
|
1176
|
-
log("Payment signature received, verifying for pass purchase...");
|
|
1177
|
-
const verifyResult = await server.verifyPayment(paymentSignature);
|
|
1178
|
-
if (!verifyResult.isValid) {
|
|
1179
|
-
log("Payment verification failed:", verifyResult.invalidReason);
|
|
1180
|
-
res.status(402).json({ error: "Payment verification failed", reason: verifyResult.invalidReason });
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
const settleResult = await server.settlePayment(paymentSignature);
|
|
1184
|
-
if (!settleResult.success) {
|
|
1185
|
-
log("Payment settlement failed:", settleResult.errorReason);
|
|
1186
|
-
res.status(402).json({ error: "Payment settlement failed", reason: settleResult.errorReason });
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
log("Payment settled:", settleResult.transaction);
|
|
1190
|
-
const pricing2 = resolvePricing(req);
|
|
1191
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1192
|
-
const claims = {
|
|
1193
|
-
sub: "x402-access-pass",
|
|
1194
|
-
tier: pricing2.tier,
|
|
1195
|
-
duration: pricing2.seconds,
|
|
1196
|
-
iat: now,
|
|
1197
|
-
exp: now + pricing2.seconds,
|
|
1198
|
-
payer: verifyResult.payer ?? "",
|
|
1199
|
-
network,
|
|
1200
|
-
iss: issuer
|
|
1201
|
-
};
|
|
1202
|
-
const jwt = signJwt(claims, secret);
|
|
1203
|
-
req.x402 = {
|
|
1204
|
-
transaction: settleResult.transaction,
|
|
1205
|
-
payer: verifyResult.payer ?? "",
|
|
1206
|
-
network
|
|
1207
|
-
};
|
|
1208
|
-
const paymentResponseData = {
|
|
1209
|
-
success: true,
|
|
1210
|
-
transaction: settleResult.transaction,
|
|
1211
|
-
network,
|
|
1212
|
-
payer: verifyResult.payer ?? ""
|
|
1213
|
-
};
|
|
1214
|
-
res.setHeader("PAYMENT-RESPONSE", encodeBase64Json(paymentResponseData));
|
|
1215
|
-
res.setHeader("ACCESS-PASS", jwt);
|
|
1216
|
-
res.json({
|
|
1217
|
-
accessPass: {
|
|
1218
|
-
token: jwt,
|
|
1219
|
-
tier: pricing2.tier,
|
|
1220
|
-
duration: pricing2.label,
|
|
1221
|
-
durationSeconds: pricing2.seconds,
|
|
1222
|
-
expiresAt: new Date((now + pricing2.seconds) * 1e3).toISOString(),
|
|
1223
|
-
usage: "Include on subsequent requests as: Authorization: Bearer <token>"
|
|
1224
|
-
},
|
|
1225
|
-
transaction: settleResult.transaction,
|
|
1226
|
-
payer: verifyResult.payer
|
|
1227
|
-
});
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
log("No access pass or payment, returning 402");
|
|
1231
|
-
const pricing = resolvePricing(req);
|
|
1232
|
-
const amountAtomic = pricing.priceAtomic;
|
|
1233
|
-
const resourceUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
|
|
1234
|
-
const requirements = await server.buildRequirements({
|
|
1235
|
-
amountAtomic,
|
|
1236
|
-
resourceUrl,
|
|
1237
|
-
description: description || `Access pass: ${pricing.label}`,
|
|
1238
|
-
mimeType: "application/json"
|
|
1239
|
-
});
|
|
1240
|
-
const encoded = server.encodeRequirements(requirements);
|
|
1241
|
-
res.setHeader("PAYMENT-REQUIRED", encoded);
|
|
1242
|
-
res.setHeader("X-ACCESS-PASS-TIERS", passInfoEncoded);
|
|
1243
|
-
res.status(402).json({
|
|
1244
|
-
error: "Access pass required",
|
|
1245
|
-
message: "Purchase an access pass to unlock unlimited API access for a time window.",
|
|
1246
|
-
accepts: requirements.accepts,
|
|
1247
|
-
resource: requirements.resource,
|
|
1248
|
-
accessPass: {
|
|
1249
|
-
tiers: builtTiers.length > 0 ? builtTiers : void 0,
|
|
1250
|
-
ratePerHour: ratePerHour || void 0,
|
|
1251
|
-
usage: "Add ?tier=<id> or ?duration=<seconds> to your payment request to choose a pass duration."
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
} catch (error) {
|
|
1255
|
-
log("Access pass middleware error:", error);
|
|
1256
|
-
res.status(500).json({
|
|
1257
|
-
error: "Payment processing error",
|
|
1258
|
-
message: error instanceof Error ? error.message : "Unknown error"
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
// src/server/dynamic-pricing.ts
|
|
1265
|
-
import { createHmac, randomBytes } from "crypto";
|
|
1266
|
-
var QUOTE_MAX_AGE_SECONDS = 300;
|
|
1267
|
-
function createDynamicPricing(config) {
|
|
1268
|
-
const fullConfig = {
|
|
1269
|
-
unitSize: config.unitSize,
|
|
1270
|
-
ratePerUnit: config.ratePerUnit,
|
|
1271
|
-
minUsd: config.minUsd ?? 0.01,
|
|
1272
|
-
maxUsd: config.maxUsd ?? Infinity,
|
|
1273
|
-
roundingMode: config.roundingMode ?? "ceil",
|
|
1274
|
-
decimals: config.decimals ?? 6
|
|
1275
|
-
};
|
|
1276
|
-
const { unitSize, ratePerUnit, minUsd, maxUsd, roundingMode, decimals } = fullConfig;
|
|
1277
|
-
if (unitSize <= 0) throw new Error("unitSize must be positive");
|
|
1278
|
-
if (ratePerUnit <= 0) throw new Error("ratePerUnit must be positive");
|
|
1279
|
-
if (minUsd < 0) throw new Error("minUsd cannot be negative");
|
|
1280
|
-
if (maxUsd < minUsd) throw new Error("maxUsd must be >= minUsd");
|
|
1281
|
-
const hmacSecret = randomBytes(32);
|
|
1282
|
-
function signQuote(input, timestamp) {
|
|
1283
|
-
const configStr = JSON.stringify({
|
|
1284
|
-
unitSize,
|
|
1285
|
-
ratePerUnit,
|
|
1286
|
-
minUsd,
|
|
1287
|
-
maxUsd: maxUsd === Infinity ? "none" : maxUsd,
|
|
1288
|
-
roundingMode
|
|
1289
|
-
});
|
|
1290
|
-
const data = `${input}|${configStr}|${timestamp}`;
|
|
1291
|
-
return createHmac("sha256", hmacSecret).update(data).digest("hex").slice(0, 16);
|
|
1292
|
-
}
|
|
1293
|
-
function calculate(input) {
|
|
1294
|
-
const inputLength = input.length;
|
|
1295
|
-
const rawUnits = inputLength / unitSize;
|
|
1296
|
-
let units;
|
|
1297
|
-
switch (roundingMode) {
|
|
1298
|
-
case "ceil":
|
|
1299
|
-
units = Math.ceil(rawUnits);
|
|
1300
|
-
break;
|
|
1301
|
-
case "floor":
|
|
1302
|
-
units = Math.floor(rawUnits);
|
|
1303
|
-
break;
|
|
1304
|
-
case "round":
|
|
1305
|
-
units = Math.round(rawUnits);
|
|
1306
|
-
break;
|
|
1307
|
-
}
|
|
1308
|
-
if (inputLength > 0 && units === 0) {
|
|
1309
|
-
units = 1;
|
|
1310
|
-
}
|
|
1311
|
-
let usdAmount = units * ratePerUnit;
|
|
1312
|
-
usdAmount = Math.max(minUsd, usdAmount);
|
|
1313
|
-
usdAmount = Math.min(maxUsd, usdAmount);
|
|
1314
|
-
const multiplier = Math.pow(10, decimals);
|
|
1315
|
-
const amountAtomic = Math.floor(usdAmount * multiplier).toString();
|
|
1316
|
-
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1317
|
-
const mac = signQuote(input, timestamp);
|
|
1318
|
-
const quoteHash = `${timestamp}.${mac}`;
|
|
1319
|
-
return {
|
|
1320
|
-
amountAtomic,
|
|
1321
|
-
usdAmount,
|
|
1322
|
-
quoteHash,
|
|
1323
|
-
units,
|
|
1324
|
-
inputLength
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
function validateQuote(input, quoteHash) {
|
|
1328
|
-
if (!quoteHash) return false;
|
|
1329
|
-
const dotIndex = quoteHash.indexOf(".");
|
|
1330
|
-
if (dotIndex === -1) return false;
|
|
1331
|
-
const timestamp = parseInt(quoteHash.slice(0, dotIndex), 10);
|
|
1332
|
-
const mac = quoteHash.slice(dotIndex + 1);
|
|
1333
|
-
if (isNaN(timestamp) || !mac) return false;
|
|
1334
|
-
const age = Math.floor(Date.now() / 1e3) - timestamp;
|
|
1335
|
-
if (age < 0 || age > QUOTE_MAX_AGE_SECONDS) return false;
|
|
1336
|
-
const expectedMac = signQuote(input, timestamp);
|
|
1337
|
-
if (mac.length !== expectedMac.length) return false;
|
|
1338
|
-
let mismatch = 0;
|
|
1339
|
-
for (let i = 0; i < mac.length; i++) {
|
|
1340
|
-
mismatch |= mac.charCodeAt(i) ^ expectedMac.charCodeAt(i);
|
|
1341
|
-
}
|
|
1342
|
-
return mismatch === 0;
|
|
1343
|
-
}
|
|
1344
|
-
return {
|
|
1345
|
-
calculate,
|
|
1346
|
-
validateQuote,
|
|
1347
|
-
config: fullConfig
|
|
1348
|
-
};
|
|
1349
|
-
}
|
|
1350
|
-
function formatPricing(config) {
|
|
1351
|
-
const rate = config.ratePerUnit.toFixed(2);
|
|
1352
|
-
const units = config.unitSize.toLocaleString();
|
|
1353
|
-
return `from $${rate} per ${units} chars`;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// src/server/token-pricing.ts
|
|
1357
|
-
import { createHash } from "crypto";
|
|
1358
|
-
|
|
1359
|
-
// src/server/model-registry.ts
|
|
1360
|
-
var STANDARD_PARAMS = {
|
|
1361
|
-
usesMaxCompletionTokens: false,
|
|
1362
|
-
supportsTemperature: true,
|
|
1363
|
-
supportsTopP: true,
|
|
1364
|
-
supportsFrequencyPenalty: true,
|
|
1365
|
-
supportsPresencePenalty: true,
|
|
1366
|
-
supportsReasoningEffort: false,
|
|
1367
|
-
supportsStreaming: true,
|
|
1368
|
-
supportsSystemMessage: true,
|
|
1369
|
-
supportsTools: true,
|
|
1370
|
-
supportsStructuredOutput: true
|
|
1371
|
-
};
|
|
1372
|
-
var GPT5_PARAMS = {
|
|
1373
|
-
usesMaxCompletionTokens: true,
|
|
1374
|
-
// GPT-5 requires this!
|
|
1375
|
-
supportsTemperature: false,
|
|
1376
|
-
// Only default (1) is supported
|
|
1377
|
-
supportsTopP: false,
|
|
1378
|
-
// Likely same restriction
|
|
1379
|
-
supportsFrequencyPenalty: false,
|
|
1380
|
-
supportsPresencePenalty: false,
|
|
1381
|
-
supportsReasoningEffort: false,
|
|
1382
|
-
supportsStreaming: true,
|
|
1383
|
-
supportsSystemMessage: true,
|
|
1384
|
-
supportsTools: true,
|
|
1385
|
-
supportsStructuredOutput: true
|
|
1386
|
-
};
|
|
1387
|
-
var REASONING_PARAMS = {
|
|
1388
|
-
usesMaxCompletionTokens: true,
|
|
1389
|
-
supportsTemperature: false,
|
|
1390
|
-
// Fixed at 1
|
|
1391
|
-
supportsTopP: false,
|
|
1392
|
-
supportsFrequencyPenalty: false,
|
|
1393
|
-
supportsPresencePenalty: false,
|
|
1394
|
-
supportsReasoningEffort: true,
|
|
1395
|
-
supportsStreaming: true,
|
|
1396
|
-
supportsSystemMessage: true,
|
|
1397
|
-
// Developer message
|
|
1398
|
-
supportsTools: true,
|
|
1399
|
-
supportsStructuredOutput: true
|
|
1400
|
-
};
|
|
1401
|
-
var PRO_REASONING_PARAMS = {
|
|
1402
|
-
...REASONING_PARAMS,
|
|
1403
|
-
supportsStreaming: false
|
|
1404
|
-
// Pro models may not stream
|
|
1405
|
-
};
|
|
1406
|
-
var MODEL_REGISTRY = [
|
|
1407
|
-
// =========================================================================
|
|
1408
|
-
// FAST TIER - Cheapest, fastest, good for simple tasks
|
|
1409
|
-
// =========================================================================
|
|
1410
|
-
{
|
|
1411
|
-
id: "gpt-4o-mini",
|
|
1412
|
-
displayName: "GPT-4o Mini",
|
|
1413
|
-
family: "gpt-4o",
|
|
1414
|
-
tier: "fast",
|
|
1415
|
-
capabilityRank: 3,
|
|
1416
|
-
modalities: ["text", "vision"],
|
|
1417
|
-
apiType: "chat",
|
|
1418
|
-
pricing: { input: 0.15, output: 0.6, cached: 0.075 },
|
|
1419
|
-
contextWindow: 128e3,
|
|
1420
|
-
defaultMaxOutput: 4096,
|
|
1421
|
-
maxOutputTokens: 16384,
|
|
1422
|
-
parameters: STANDARD_PARAMS,
|
|
1423
|
-
deprecated: false,
|
|
1424
|
-
description: "Fast, affordable small model with vision support"
|
|
1425
|
-
},
|
|
1426
|
-
{
|
|
1427
|
-
id: "gpt-4.1-nano",
|
|
1428
|
-
displayName: "GPT-4.1 Nano",
|
|
1429
|
-
family: "gpt-4.1",
|
|
1430
|
-
tier: "fast",
|
|
1431
|
-
capabilityRank: 2,
|
|
1432
|
-
modalities: ["text"],
|
|
1433
|
-
apiType: "chat",
|
|
1434
|
-
pricing: { input: 0.1, output: 0.4, cached: 0.025 },
|
|
1435
|
-
contextWindow: 128e3,
|
|
1436
|
-
defaultMaxOutput: 4096,
|
|
1437
|
-
maxOutputTokens: 32768,
|
|
1438
|
-
parameters: STANDARD_PARAMS,
|
|
1439
|
-
deprecated: false,
|
|
1440
|
-
description: "Smallest 4.1 model, very fast and cheap"
|
|
1441
|
-
},
|
|
1442
|
-
{
|
|
1443
|
-
id: "gpt-4.1-mini",
|
|
1444
|
-
displayName: "GPT-4.1 Mini",
|
|
1445
|
-
family: "gpt-4.1",
|
|
1446
|
-
tier: "fast",
|
|
1447
|
-
capabilityRank: 4,
|
|
1448
|
-
modalities: ["text"],
|
|
1449
|
-
apiType: "chat",
|
|
1450
|
-
pricing: { input: 0.4, output: 1.6, cached: 0.1 },
|
|
1451
|
-
contextWindow: 128e3,
|
|
1452
|
-
defaultMaxOutput: 4096,
|
|
1453
|
-
maxOutputTokens: 32768,
|
|
1454
|
-
parameters: STANDARD_PARAMS,
|
|
1455
|
-
deprecated: false,
|
|
1456
|
-
description: "Balanced 4.1 model, good price/performance"
|
|
1457
|
-
},
|
|
1458
|
-
{
|
|
1459
|
-
id: "gpt-5-nano",
|
|
1460
|
-
displayName: "GPT-5 Nano",
|
|
1461
|
-
family: "gpt-5",
|
|
1462
|
-
tier: "fast",
|
|
1463
|
-
capabilityRank: 1,
|
|
1464
|
-
modalities: ["text"],
|
|
1465
|
-
apiType: "chat",
|
|
1466
|
-
pricing: { input: 0.05, output: 0.4, cached: 5e-3 },
|
|
1467
|
-
contextWindow: 128e3,
|
|
1468
|
-
defaultMaxOutput: 4096,
|
|
1469
|
-
maxOutputTokens: 16384,
|
|
1470
|
-
parameters: GPT5_PARAMS,
|
|
1471
|
-
deprecated: false,
|
|
1472
|
-
description: "Cheapest GPT-5 variant, extremely fast"
|
|
1473
|
-
},
|
|
1474
|
-
{
|
|
1475
|
-
id: "gpt-5-mini",
|
|
1476
|
-
displayName: "GPT-5 Mini",
|
|
1477
|
-
family: "gpt-5",
|
|
1478
|
-
tier: "fast",
|
|
1479
|
-
capabilityRank: 5,
|
|
1480
|
-
modalities: ["text"],
|
|
1481
|
-
apiType: "chat",
|
|
1482
|
-
pricing: { input: 0.25, output: 2, cached: 0.025 },
|
|
1483
|
-
contextWindow: 128e3,
|
|
1484
|
-
defaultMaxOutput: 8192,
|
|
1485
|
-
maxOutputTokens: 32768,
|
|
1486
|
-
parameters: GPT5_PARAMS,
|
|
1487
|
-
deprecated: false,
|
|
1488
|
-
description: "Small but capable GPT-5, great value"
|
|
1489
|
-
},
|
|
1490
|
-
// =========================================================================
|
|
1491
|
-
// STANDARD TIER - Balanced price/performance
|
|
1492
|
-
// =========================================================================
|
|
1493
|
-
{
|
|
1494
|
-
id: "gpt-4o",
|
|
1495
|
-
displayName: "GPT-4o",
|
|
1496
|
-
family: "gpt-4o",
|
|
1497
|
-
tier: "standard",
|
|
1498
|
-
capabilityRank: 5,
|
|
1499
|
-
modalities: ["text", "vision"],
|
|
1500
|
-
apiType: "chat",
|
|
1501
|
-
pricing: { input: 2.5, output: 10, cached: 1.25 },
|
|
1502
|
-
contextWindow: 128e3,
|
|
1503
|
-
defaultMaxOutput: 4096,
|
|
1504
|
-
maxOutputTokens: 16384,
|
|
1505
|
-
parameters: STANDARD_PARAMS,
|
|
1506
|
-
deprecated: false,
|
|
1507
|
-
description: "Flagship multimodal model with vision"
|
|
1508
|
-
},
|
|
1509
|
-
{
|
|
1510
|
-
id: "gpt-4.1",
|
|
1511
|
-
displayName: "GPT-4.1",
|
|
1512
|
-
family: "gpt-4.1",
|
|
1513
|
-
tier: "standard",
|
|
1514
|
-
capabilityRank: 6,
|
|
1515
|
-
modalities: ["text"],
|
|
1516
|
-
apiType: "chat",
|
|
1517
|
-
pricing: { input: 2, output: 8, cached: 0.5 },
|
|
1518
|
-
contextWindow: 1e6,
|
|
1519
|
-
// 1M context!
|
|
1520
|
-
defaultMaxOutput: 8192,
|
|
1521
|
-
maxOutputTokens: 32768,
|
|
1522
|
-
parameters: STANDARD_PARAMS,
|
|
1523
|
-
deprecated: false,
|
|
1524
|
-
description: "Long context specialist, 1M token window"
|
|
1525
|
-
},
|
|
1526
|
-
{
|
|
1527
|
-
id: "gpt-5",
|
|
1528
|
-
displayName: "GPT-5",
|
|
1529
|
-
family: "gpt-5",
|
|
1530
|
-
tier: "standard",
|
|
1531
|
-
capabilityRank: 7,
|
|
1532
|
-
modalities: ["text"],
|
|
1533
|
-
apiType: "chat",
|
|
1534
|
-
pricing: { input: 1.25, output: 10, cached: 0.125 },
|
|
1535
|
-
contextWindow: 128e3,
|
|
1536
|
-
defaultMaxOutput: 8192,
|
|
1537
|
-
maxOutputTokens: 32768,
|
|
1538
|
-
parameters: GPT5_PARAMS,
|
|
1539
|
-
deprecated: false,
|
|
1540
|
-
description: "Base GPT-5, excellent all-around"
|
|
1541
|
-
},
|
|
1542
|
-
{
|
|
1543
|
-
id: "gpt-5.1",
|
|
1544
|
-
displayName: "GPT-5.1",
|
|
1545
|
-
family: "gpt-5",
|
|
1546
|
-
tier: "standard",
|
|
1547
|
-
capabilityRank: 8,
|
|
1548
|
-
modalities: ["text"],
|
|
1549
|
-
apiType: "chat",
|
|
1550
|
-
pricing: { input: 1.25, output: 10, cached: 0.125 },
|
|
1551
|
-
contextWindow: 128e3,
|
|
1552
|
-
defaultMaxOutput: 8192,
|
|
1553
|
-
maxOutputTokens: 32768,
|
|
1554
|
-
parameters: GPT5_PARAMS,
|
|
1555
|
-
deprecated: false,
|
|
1556
|
-
description: "Improved GPT-5 with better instruction following"
|
|
1557
|
-
},
|
|
1558
|
-
{
|
|
1559
|
-
id: "gpt-5.2",
|
|
1560
|
-
displayName: "GPT-5.2",
|
|
1561
|
-
family: "gpt-5",
|
|
1562
|
-
tier: "standard",
|
|
1563
|
-
capabilityRank: 9,
|
|
1564
|
-
modalities: ["text"],
|
|
1565
|
-
apiType: "chat",
|
|
1566
|
-
pricing: { input: 1.75, output: 14, cached: 0.175 },
|
|
1567
|
-
contextWindow: 128e3,
|
|
1568
|
-
defaultMaxOutput: 8192,
|
|
1569
|
-
maxOutputTokens: 32768,
|
|
1570
|
-
parameters: GPT5_PARAMS,
|
|
1571
|
-
deprecated: false,
|
|
1572
|
-
description: "Latest GPT-5, most capable standard model"
|
|
1573
|
-
},
|
|
1574
|
-
// =========================================================================
|
|
1575
|
-
// REASONING TIER - Chain-of-thought reasoning (o-series)
|
|
1576
|
-
// =========================================================================
|
|
1577
|
-
{
|
|
1578
|
-
id: "o1-mini",
|
|
1579
|
-
displayName: "o1 Mini",
|
|
1580
|
-
family: "o1",
|
|
1581
|
-
tier: "reasoning",
|
|
1582
|
-
capabilityRank: 3,
|
|
1583
|
-
modalities: ["text"],
|
|
1584
|
-
apiType: "chat",
|
|
1585
|
-
pricing: { input: 1.1, output: 4.4, cached: 0.55 },
|
|
1586
|
-
contextWindow: 128e3,
|
|
1587
|
-
defaultMaxOutput: 16384,
|
|
1588
|
-
maxOutputTokens: 65536,
|
|
1589
|
-
parameters: REASONING_PARAMS,
|
|
1590
|
-
deprecated: false,
|
|
1591
|
-
description: "Fast reasoning model, good for math/code"
|
|
1592
|
-
},
|
|
1593
|
-
{
|
|
1594
|
-
id: "o3-mini",
|
|
1595
|
-
displayName: "o3 Mini",
|
|
1596
|
-
family: "o3",
|
|
1597
|
-
tier: "reasoning",
|
|
1598
|
-
capabilityRank: 4,
|
|
1599
|
-
modalities: ["text"],
|
|
1600
|
-
apiType: "chat",
|
|
1601
|
-
pricing: { input: 1.1, output: 4.4, cached: 0.55 },
|
|
1602
|
-
contextWindow: 128e3,
|
|
1603
|
-
defaultMaxOutput: 16384,
|
|
1604
|
-
maxOutputTokens: 65536,
|
|
1605
|
-
parameters: REASONING_PARAMS,
|
|
1606
|
-
deprecated: false,
|
|
1607
|
-
description: "Improved mini reasoner with better efficiency"
|
|
1608
|
-
},
|
|
1609
|
-
{
|
|
1610
|
-
id: "o4-mini",
|
|
1611
|
-
displayName: "o4 Mini",
|
|
1612
|
-
family: "o4",
|
|
1613
|
-
tier: "reasoning",
|
|
1614
|
-
capabilityRank: 5,
|
|
1615
|
-
modalities: ["text"],
|
|
1616
|
-
apiType: "chat",
|
|
1617
|
-
pricing: { input: 1.1, output: 4.4, cached: 0.275 },
|
|
1618
|
-
contextWindow: 128e3,
|
|
1619
|
-
defaultMaxOutput: 16384,
|
|
1620
|
-
maxOutputTokens: 65536,
|
|
1621
|
-
parameters: REASONING_PARAMS,
|
|
1622
|
-
deprecated: false,
|
|
1623
|
-
description: "Latest mini reasoner, best reasoning per dollar"
|
|
1624
|
-
},
|
|
1625
|
-
{
|
|
1626
|
-
id: "o3",
|
|
1627
|
-
displayName: "o3",
|
|
1628
|
-
family: "o3",
|
|
1629
|
-
tier: "reasoning",
|
|
1630
|
-
capabilityRank: 7,
|
|
1631
|
-
modalities: ["text"],
|
|
1632
|
-
apiType: "chat",
|
|
1633
|
-
pricing: { input: 2, output: 8, cached: 0.5 },
|
|
1634
|
-
contextWindow: 2e5,
|
|
1635
|
-
defaultMaxOutput: 32768,
|
|
1636
|
-
maxOutputTokens: 1e5,
|
|
1637
|
-
parameters: REASONING_PARAMS,
|
|
1638
|
-
deprecated: false,
|
|
1639
|
-
description: "Full o3 reasoning model, excellent for complex problems"
|
|
1640
|
-
},
|
|
1641
|
-
{
|
|
1642
|
-
id: "o1",
|
|
1643
|
-
displayName: "o1",
|
|
1644
|
-
family: "o1",
|
|
1645
|
-
tier: "reasoning",
|
|
1646
|
-
capabilityRank: 8,
|
|
1647
|
-
modalities: ["text"],
|
|
1648
|
-
apiType: "chat",
|
|
1649
|
-
pricing: { input: 15, output: 60, cached: 7.5 },
|
|
1650
|
-
contextWindow: 2e5,
|
|
1651
|
-
defaultMaxOutput: 32768,
|
|
1652
|
-
maxOutputTokens: 1e5,
|
|
1653
|
-
parameters: REASONING_PARAMS,
|
|
1654
|
-
deprecated: false,
|
|
1655
|
-
description: "Original full reasoning model, very capable"
|
|
1656
|
-
},
|
|
1657
|
-
// =========================================================================
|
|
1658
|
-
// PREMIUM TIER - Most capable, expensive
|
|
1659
|
-
// =========================================================================
|
|
1660
|
-
{
|
|
1661
|
-
id: "gpt-5-pro",
|
|
1662
|
-
displayName: "GPT-5 Pro",
|
|
1663
|
-
family: "gpt-5",
|
|
1664
|
-
tier: "premium",
|
|
1665
|
-
capabilityRank: 7,
|
|
1666
|
-
modalities: ["text"],
|
|
1667
|
-
apiType: "chat",
|
|
1668
|
-
pricing: { input: 15, output: 120 },
|
|
1669
|
-
contextWindow: 128e3,
|
|
1670
|
-
defaultMaxOutput: 16384,
|
|
1671
|
-
maxOutputTokens: 32768,
|
|
1672
|
-
parameters: GPT5_PARAMS,
|
|
1673
|
-
deprecated: false,
|
|
1674
|
-
description: "Enhanced GPT-5 for demanding tasks"
|
|
1675
|
-
},
|
|
1676
|
-
{
|
|
1677
|
-
id: "gpt-5.2-pro",
|
|
1678
|
-
displayName: "GPT-5.2 Pro",
|
|
1679
|
-
family: "gpt-5",
|
|
1680
|
-
tier: "premium",
|
|
1681
|
-
capabilityRank: 8,
|
|
1682
|
-
modalities: ["text"],
|
|
1683
|
-
apiType: "chat",
|
|
1684
|
-
pricing: { input: 21, output: 168 },
|
|
1685
|
-
contextWindow: 128e3,
|
|
1686
|
-
defaultMaxOutput: 16384,
|
|
1687
|
-
maxOutputTokens: 32768,
|
|
1688
|
-
parameters: GPT5_PARAMS,
|
|
1689
|
-
deprecated: false,
|
|
1690
|
-
description: "Most capable standard model available"
|
|
1691
|
-
},
|
|
1692
|
-
{
|
|
1693
|
-
id: "o3-pro",
|
|
1694
|
-
displayName: "o3 Pro",
|
|
1695
|
-
family: "o3",
|
|
1696
|
-
tier: "premium",
|
|
1697
|
-
capabilityRank: 9,
|
|
1698
|
-
modalities: ["text"],
|
|
1699
|
-
apiType: "chat",
|
|
1700
|
-
pricing: { input: 20, output: 80 },
|
|
1701
|
-
contextWindow: 2e5,
|
|
1702
|
-
defaultMaxOutput: 32768,
|
|
1703
|
-
maxOutputTokens: 1e5,
|
|
1704
|
-
parameters: PRO_REASONING_PARAMS,
|
|
1705
|
-
deprecated: false,
|
|
1706
|
-
description: "Premium o3 with extended thinking time"
|
|
1707
|
-
},
|
|
1708
|
-
{
|
|
1709
|
-
id: "o1-pro",
|
|
1710
|
-
displayName: "o1 Pro",
|
|
1711
|
-
family: "o1",
|
|
1712
|
-
tier: "premium",
|
|
1713
|
-
capabilityRank: 10,
|
|
1714
|
-
modalities: ["text"],
|
|
1715
|
-
apiType: "chat",
|
|
1716
|
-
pricing: { input: 150, output: 600 },
|
|
1717
|
-
contextWindow: 2e5,
|
|
1718
|
-
defaultMaxOutput: 32768,
|
|
1719
|
-
maxOutputTokens: 1e5,
|
|
1720
|
-
parameters: PRO_REASONING_PARAMS,
|
|
1721
|
-
deprecated: false,
|
|
1722
|
-
description: "Most capable reasoning model, extended compute"
|
|
1723
|
-
},
|
|
1724
|
-
// =========================================================================
|
|
1725
|
-
// SPECIALIZED TIER - Special purpose models
|
|
1726
|
-
// =========================================================================
|
|
1727
|
-
{
|
|
1728
|
-
id: "o3-deep-research",
|
|
1729
|
-
displayName: "o3 Deep Research",
|
|
1730
|
-
family: "o3",
|
|
1731
|
-
tier: "specialized",
|
|
1732
|
-
capabilityRank: 8,
|
|
1733
|
-
modalities: ["text"],
|
|
1734
|
-
apiType: "responses",
|
|
1735
|
-
pricing: { input: 10, output: 40, cached: 2.5 },
|
|
1736
|
-
contextWindow: 2e5,
|
|
1737
|
-
defaultMaxOutput: 32768,
|
|
1738
|
-
maxOutputTokens: 1e5,
|
|
1739
|
-
parameters: {
|
|
1740
|
-
...REASONING_PARAMS,
|
|
1741
|
-
supportsStreaming: false
|
|
1742
|
-
},
|
|
1743
|
-
deprecated: false,
|
|
1744
|
-
description: "Extended research sessions with web access"
|
|
1745
|
-
},
|
|
1746
|
-
{
|
|
1747
|
-
id: "o4-mini-deep-research",
|
|
1748
|
-
displayName: "o4 Mini Deep Research",
|
|
1749
|
-
family: "o4",
|
|
1750
|
-
tier: "specialized",
|
|
1751
|
-
capabilityRank: 6,
|
|
1752
|
-
modalities: ["text"],
|
|
1753
|
-
apiType: "responses",
|
|
1754
|
-
pricing: { input: 2, output: 8, cached: 0.5 },
|
|
1755
|
-
contextWindow: 128e3,
|
|
1756
|
-
defaultMaxOutput: 16384,
|
|
1757
|
-
maxOutputTokens: 65536,
|
|
1758
|
-
parameters: {
|
|
1759
|
-
...REASONING_PARAMS,
|
|
1760
|
-
supportsStreaming: false
|
|
1761
|
-
},
|
|
1762
|
-
deprecated: false,
|
|
1763
|
-
description: "Affordable deep research with o4 mini"
|
|
1764
|
-
},
|
|
1765
|
-
{
|
|
1766
|
-
id: "computer-use-preview",
|
|
1767
|
-
displayName: "Computer Use Preview",
|
|
1768
|
-
family: "computer-use",
|
|
1769
|
-
tier: "specialized",
|
|
1770
|
-
capabilityRank: 5,
|
|
1771
|
-
modalities: ["text", "vision"],
|
|
1772
|
-
apiType: "responses",
|
|
1773
|
-
pricing: { input: 3, output: 12 },
|
|
1774
|
-
contextWindow: 128e3,
|
|
1775
|
-
defaultMaxOutput: 4096,
|
|
1776
|
-
maxOutputTokens: 16384,
|
|
1777
|
-
parameters: {
|
|
1778
|
-
...STANDARD_PARAMS,
|
|
1779
|
-
supportsReasoningEffort: false
|
|
1780
|
-
},
|
|
1781
|
-
deprecated: false,
|
|
1782
|
-
description: "Can control computer interfaces via screenshots"
|
|
1783
|
-
},
|
|
1784
|
-
// =========================================================================
|
|
1785
|
-
// REALTIME TIER - Real-time audio/video
|
|
1786
|
-
// =========================================================================
|
|
1787
|
-
{
|
|
1788
|
-
id: "gpt-realtime",
|
|
1789
|
-
displayName: "GPT Realtime",
|
|
1790
|
-
family: "gpt-realtime",
|
|
1791
|
-
tier: "specialized",
|
|
1792
|
-
capabilityRank: 7,
|
|
1793
|
-
modalities: ["text", "audio", "realtime"],
|
|
1794
|
-
apiType: "chat",
|
|
1795
|
-
pricing: { input: 4, output: 16, cached: 0.4 },
|
|
1796
|
-
contextWindow: 128e3,
|
|
1797
|
-
defaultMaxOutput: 4096,
|
|
1798
|
-
maxOutputTokens: 4096,
|
|
1799
|
-
parameters: {
|
|
1800
|
-
...STANDARD_PARAMS,
|
|
1801
|
-
supportsReasoningEffort: false
|
|
1802
|
-
},
|
|
1803
|
-
deprecated: false,
|
|
1804
|
-
description: "Real-time audio conversation model"
|
|
1805
|
-
},
|
|
1806
|
-
{
|
|
1807
|
-
id: "gpt-realtime-mini",
|
|
1808
|
-
displayName: "GPT Realtime Mini",
|
|
1809
|
-
family: "gpt-realtime",
|
|
1810
|
-
tier: "specialized",
|
|
1811
|
-
capabilityRank: 4,
|
|
1812
|
-
modalities: ["text", "audio", "realtime"],
|
|
1813
|
-
apiType: "chat",
|
|
1814
|
-
pricing: { input: 0.6, output: 2.4, cached: 0.06 },
|
|
1815
|
-
contextWindow: 128e3,
|
|
1816
|
-
defaultMaxOutput: 4096,
|
|
1817
|
-
maxOutputTokens: 4096,
|
|
1818
|
-
parameters: {
|
|
1819
|
-
...STANDARD_PARAMS,
|
|
1820
|
-
supportsReasoningEffort: false
|
|
1821
|
-
},
|
|
1822
|
-
deprecated: false,
|
|
1823
|
-
description: "Affordable real-time audio model"
|
|
1824
|
-
},
|
|
1825
|
-
// =========================================================================
|
|
1826
|
-
// LEGACY MODELS - Still available but older
|
|
1827
|
-
// =========================================================================
|
|
1828
|
-
{
|
|
1829
|
-
id: "gpt-4o-2024-05-13",
|
|
1830
|
-
displayName: "GPT-4o (May 2024)",
|
|
1831
|
-
family: "gpt-4o",
|
|
1832
|
-
tier: "standard",
|
|
1833
|
-
capabilityRank: 4,
|
|
1834
|
-
modalities: ["text", "vision"],
|
|
1835
|
-
apiType: "chat",
|
|
1836
|
-
pricing: { input: 5, output: 15 },
|
|
1837
|
-
contextWindow: 128e3,
|
|
1838
|
-
defaultMaxOutput: 4096,
|
|
1839
|
-
maxOutputTokens: 4096,
|
|
1840
|
-
parameters: STANDARD_PARAMS,
|
|
1841
|
-
deprecated: true,
|
|
1842
|
-
description: "Original GPT-4o snapshot, use gpt-4o instead"
|
|
1843
|
-
}
|
|
1844
|
-
];
|
|
1845
|
-
var MODEL_MAP = new Map(
|
|
1846
|
-
MODEL_REGISTRY.map((m) => [m.id, m])
|
|
1847
|
-
);
|
|
1848
|
-
function getModel(modelId) {
|
|
1849
|
-
const model = MODEL_MAP.get(modelId);
|
|
1850
|
-
if (!model) {
|
|
1851
|
-
throw new Error(`Unknown model: ${modelId}. Use getAvailableModelIds() to see valid options.`);
|
|
1852
|
-
}
|
|
1853
|
-
return model;
|
|
1854
|
-
}
|
|
1855
|
-
function findModel(modelId) {
|
|
1856
|
-
return MODEL_MAP.get(modelId);
|
|
1857
|
-
}
|
|
1858
|
-
function isValidModelId(modelId) {
|
|
1859
|
-
return MODEL_MAP.has(modelId);
|
|
1860
|
-
}
|
|
1861
|
-
function getAvailableModelIds() {
|
|
1862
|
-
return MODEL_REGISTRY.map((m) => m.id);
|
|
1863
|
-
}
|
|
1864
|
-
function getModelsByTier(tier) {
|
|
1865
|
-
return MODEL_REGISTRY.filter((m) => m.tier === tier && !m.deprecated).sort((a, b) => a.capabilityRank - b.capabilityRank);
|
|
1866
|
-
}
|
|
1867
|
-
function getModelsByFamily(family) {
|
|
1868
|
-
return MODEL_REGISTRY.filter((m) => m.family === family).sort((a, b) => a.capabilityRank - b.capabilityRank);
|
|
1869
|
-
}
|
|
1870
|
-
function getActiveModels() {
|
|
1871
|
-
const tierOrder = {
|
|
1872
|
-
fast: 1,
|
|
1873
|
-
standard: 2,
|
|
1874
|
-
reasoning: 3,
|
|
1875
|
-
premium: 4,
|
|
1876
|
-
specialized: 5
|
|
1877
|
-
};
|
|
1878
|
-
return MODEL_REGISTRY.filter((m) => !m.deprecated).sort((a, b) => {
|
|
1879
|
-
const tierDiff = tierOrder[a.tier] - tierOrder[b.tier];
|
|
1880
|
-
if (tierDiff !== 0) return tierDiff;
|
|
1881
|
-
return a.capabilityRank - b.capabilityRank;
|
|
1882
|
-
});
|
|
1883
|
-
}
|
|
1884
|
-
function getTextModels() {
|
|
1885
|
-
return MODEL_REGISTRY.filter(
|
|
1886
|
-
(m) => !m.deprecated && m.modalities.includes("text") && !m.modalities.includes("realtime") && m.apiType !== "responses"
|
|
1887
|
-
// Standard chat API
|
|
1888
|
-
).sort((a, b) => {
|
|
1889
|
-
const tierOrder = {
|
|
1890
|
-
fast: 1,
|
|
1891
|
-
standard: 2,
|
|
1892
|
-
reasoning: 3,
|
|
1893
|
-
premium: 4,
|
|
1894
|
-
specialized: 5
|
|
1895
|
-
};
|
|
1896
|
-
const tierDiff = tierOrder[a.tier] - tierOrder[b.tier];
|
|
1897
|
-
if (tierDiff !== 0) return tierDiff;
|
|
1898
|
-
return a.capabilityRank - b.capabilityRank;
|
|
1899
|
-
});
|
|
1900
|
-
}
|
|
1901
|
-
function getCheapestModel(minTier = "fast") {
|
|
1902
|
-
const tierOrder = {
|
|
1903
|
-
fast: 1,
|
|
1904
|
-
standard: 2,
|
|
1905
|
-
reasoning: 3,
|
|
1906
|
-
premium: 4,
|
|
1907
|
-
specialized: 5
|
|
1908
|
-
};
|
|
1909
|
-
const minTierNum = tierOrder[minTier];
|
|
1910
|
-
const candidates = MODEL_REGISTRY.filter((m) => !m.deprecated && tierOrder[m.tier] >= minTierNum).sort((a, b) => a.pricing.input - b.pricing.input);
|
|
1911
|
-
return candidates[0];
|
|
1912
|
-
}
|
|
1913
|
-
function estimateCost(modelId, inputTokens, outputTokens, useCached = false) {
|
|
1914
|
-
const model = getModel(modelId);
|
|
1915
|
-
const inputCost = useCached && model.pricing.cached ? inputTokens / 1e6 * model.pricing.cached : inputTokens / 1e6 * model.pricing.input;
|
|
1916
|
-
const outputCost = outputTokens / 1e6 * model.pricing.output;
|
|
1917
|
-
return inputCost + outputCost;
|
|
1918
|
-
}
|
|
1919
|
-
function formatModelPricing(modelId) {
|
|
1920
|
-
const model = getModel(modelId);
|
|
1921
|
-
return `$${model.pricing.input.toFixed(2)} in / $${model.pricing.output.toFixed(2)} out per 1M tokens`;
|
|
1922
|
-
}
|
|
1923
|
-
var MODEL_PRICING_MAP = Object.fromEntries(
|
|
1924
|
-
MODEL_REGISTRY.map((m) => [m.id, {
|
|
1925
|
-
input: m.pricing.input,
|
|
1926
|
-
output: m.pricing.output,
|
|
1927
|
-
cached: m.pricing.cached,
|
|
1928
|
-
maxTokens: m.defaultMaxOutput,
|
|
1929
|
-
tier: m.tier
|
|
1930
|
-
}])
|
|
1931
|
-
);
|
|
1932
|
-
|
|
1933
|
-
// src/server/token-pricing.ts
|
|
1934
|
-
var _tiktoken = null;
|
|
1935
|
-
async function loadTiktoken() {
|
|
1936
|
-
if (_tiktoken) return _tiktoken;
|
|
1937
|
-
try {
|
|
1938
|
-
_tiktoken = await import("tiktoken");
|
|
1939
|
-
return _tiktoken;
|
|
1940
|
-
} catch {
|
|
1941
|
-
throw new Error(
|
|
1942
|
-
'Token pricing requires the "tiktoken" package. Install with: npm install tiktoken'
|
|
1943
|
-
);
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
var MODEL_PRICING = MODEL_PRICING_MAP;
|
|
1947
|
-
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
1948
|
-
async function getEncodingForModel(model) {
|
|
1949
|
-
const tiktoken = await loadTiktoken();
|
|
1950
|
-
try {
|
|
1951
|
-
return tiktoken.encoding_for_model(model);
|
|
1952
|
-
} catch {
|
|
1953
|
-
return tiktoken.get_encoding("cl100k_base");
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
async function countTokens(text, model = DEFAULT_MODEL) {
|
|
1957
|
-
const encoding = await getEncodingForModel(model);
|
|
1958
|
-
try {
|
|
1959
|
-
const tokens = encoding.encode(text);
|
|
1960
|
-
return tokens.length;
|
|
1961
|
-
} finally {
|
|
1962
|
-
encoding.free();
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
function generateQuoteHash(prompt, model, rate, tokens) {
|
|
1966
|
-
const configString = JSON.stringify({ model, rate, tokens });
|
|
1967
|
-
return createHash("sha256").update(prompt + configString).digest("hex").slice(0, 16);
|
|
1968
|
-
}
|
|
1969
|
-
function createTokenPricing(config = {}) {
|
|
1970
|
-
const model = config.model ?? DEFAULT_MODEL;
|
|
1971
|
-
const builtInPricing = MODEL_PRICING[model];
|
|
1972
|
-
const modelInfo = {
|
|
1973
|
-
input: config.inputRate ?? builtInPricing?.input ?? MODEL_PRICING[DEFAULT_MODEL].input,
|
|
1974
|
-
output: config.outputRate ?? builtInPricing?.output ?? MODEL_PRICING[DEFAULT_MODEL].output,
|
|
1975
|
-
maxTokens: config.maxTokens ?? builtInPricing?.maxTokens ?? 4096,
|
|
1976
|
-
tier: config.tier ?? builtInPricing?.tier ?? "custom"
|
|
1977
|
-
};
|
|
1978
|
-
const customTokenizer = config.tokenizer;
|
|
1979
|
-
const fullConfig = {
|
|
1980
|
-
model,
|
|
1981
|
-
inputRate: modelInfo.input,
|
|
1982
|
-
outputRate: modelInfo.output,
|
|
1983
|
-
maxTokens: modelInfo.maxTokens,
|
|
1984
|
-
tier: modelInfo.tier,
|
|
1985
|
-
tokenizer: customTokenizer ?? ((text) => countTokens(text, model)),
|
|
1986
|
-
minUsd: config.minUsd ?? 1e-3,
|
|
1987
|
-
maxUsd: config.maxUsd ?? 50,
|
|
1988
|
-
decimals: config.decimals ?? 6
|
|
1989
|
-
};
|
|
1990
|
-
const { minUsd, maxUsd, decimals } = fullConfig;
|
|
1991
|
-
async function countTokensInternal(input) {
|
|
1992
|
-
if (customTokenizer) {
|
|
1993
|
-
return customTokenizer(input);
|
|
1994
|
-
}
|
|
1995
|
-
return countTokens(input, model);
|
|
1996
|
-
}
|
|
1997
|
-
async function calculate(input, systemPrompt) {
|
|
1998
|
-
const fullInput = systemPrompt ? `${systemPrompt}
|
|
1999
|
-
|
|
2000
|
-
${input}` : input;
|
|
2001
|
-
const inputTokens = await countTokensInternal(fullInput);
|
|
2002
|
-
let usdAmount = inputTokens / 1e6 * modelInfo.input;
|
|
2003
|
-
usdAmount = Math.max(usdAmount, minUsd);
|
|
2004
|
-
usdAmount = Math.min(usdAmount, maxUsd);
|
|
2005
|
-
const multiplier = Math.pow(10, decimals);
|
|
2006
|
-
const amountAtomic = Math.floor(usdAmount * multiplier).toString();
|
|
2007
|
-
const quoteHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);
|
|
2008
|
-
return {
|
|
2009
|
-
amountAtomic,
|
|
2010
|
-
usdAmount,
|
|
2011
|
-
inputTokens,
|
|
2012
|
-
model,
|
|
2013
|
-
tier: modelInfo.tier,
|
|
2014
|
-
inputRatePerMillion: modelInfo.input,
|
|
2015
|
-
outputRatePerMillion: modelInfo.output,
|
|
2016
|
-
maxOutputTokens: modelInfo.maxTokens,
|
|
2017
|
-
quoteHash
|
|
2018
|
-
};
|
|
2019
|
-
}
|
|
2020
|
-
async function validateQuote(input, quoteHash) {
|
|
2021
|
-
if (!quoteHash) return false;
|
|
2022
|
-
const inputTokens = await countTokensInternal(input);
|
|
2023
|
-
const expectedHash = generateQuoteHash(input, model, modelInfo.input, inputTokens);
|
|
2024
|
-
return expectedHash === quoteHash;
|
|
2025
|
-
}
|
|
2026
|
-
return {
|
|
2027
|
-
calculate,
|
|
2028
|
-
validateQuote,
|
|
2029
|
-
countTokens: countTokensInternal,
|
|
2030
|
-
config: fullConfig,
|
|
2031
|
-
modelInfo
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
|
-
function getAvailableModels() {
|
|
2035
|
-
return Object.entries(MODEL_PRICING).map(([model, pricing]) => ({
|
|
2036
|
-
model,
|
|
2037
|
-
inputRate: pricing.input,
|
|
2038
|
-
outputRate: pricing.output,
|
|
2039
|
-
maxTokens: pricing.maxTokens,
|
|
2040
|
-
tier: pricing.tier
|
|
2041
|
-
})).sort((a, b) => {
|
|
2042
|
-
const tierOrder = { fast: 0, standard: 1, reasoning: 2, premium: 3 };
|
|
2043
|
-
const tierDiff = tierOrder[a.tier] - tierOrder[b.tier];
|
|
2044
|
-
if (tierDiff !== 0) return tierDiff;
|
|
2045
|
-
return a.inputRate - b.inputRate;
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
function isValidModel(model) {
|
|
2049
|
-
return model in MODEL_PRICING;
|
|
2050
|
-
}
|
|
2051
|
-
function formatTokenPricing(model = DEFAULT_MODEL) {
|
|
2052
|
-
const pricing = MODEL_PRICING[model] ?? MODEL_PRICING[DEFAULT_MODEL];
|
|
2053
|
-
const actualModel = MODEL_PRICING[model] ? model : DEFAULT_MODEL;
|
|
2054
|
-
return `$${pricing.input.toFixed(2)} per 1M tokens (${actualModel})`;
|
|
2055
|
-
}
|
|
284
|
+
</html>`}function Oe(e={}){let t={title:e.title??"Payment Required",branding:e.branding??'Powered by <a href="https://docs.dexter.cash/docs/sdk/">Dexter x402</a>',sdkUrl:e.sdkUrl??"https://docs.dexter.cash/docs/sdk/",showEndpoint:e.showEndpoint??!0},n=e.rpcUrl??"https://api.dexter.cash/api/solana/rpc";return(r,i,c)=>{let d=i.json.bind(i);i.json=function(g){if(i.statusCode===402&&r.accepts("html")&&!r.headers["payment-signature"]){let P=i.getHeader("PAYMENT-REQUIRED")||i.getHeader("payment-required");if(P&&typeof P=="string"){let w;if(r.body&&typeof r.body=="object"&&Object.keys(r.body).length>0)try{w=JSON.stringify(r.body)}catch{}let k=Ce(P,r.originalUrl,r.method,t,n,w);return i.setHeader("Content-Security-Policy","default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src *; img-src data:; frame-ancestors 'none'"),i.setHeader("X-Content-Type-Options","nosniff"),i.status(402).type("html").send(k),i}}return d(g)},c()}}import te from"crypto";var Ne=/^(\d+)(m|h|d|w)$/;function Ie(e){let t=e.match(Ne);if(!t)return null;let n=parseInt(t[1],10);switch(t[2]){case"m":return n*60;case"h":return n*3600;case"d":return n*86400;case"w":return n*604800;default:return null}}function de(e){return e>=604800&&e%604800===0?`${e/604800} week${e/604800>1?"s":""}`:e>=86400&&e%86400===0?`${e/86400} day${e/86400>1?"s":""}`:e>=3600&&e%3600===0?`${e/3600} hour${e/3600>1?"s":""}`:e>=60&&e%60===0?`${e/60} minute${e/60>1?"s":""}`:`${e} second${e>1?"s":""}`}function Ue(e,t){let n=Buffer.from(JSON.stringify({alg:"HS256",typ:"JWT"})).toString("base64url"),r=Buffer.from(JSON.stringify(e)).toString("base64url"),i=te.createHmac("sha256",t).update(`${n}.${r}`).digest("base64url");return`${n}.${r}.${i}`}function De(e,t){try{let n=e.split(".");if(n.length!==3)return null;let[r,i,c]=n,d=te.createHmac("sha256",t).update(`${r}.${i}`).digest("base64url");if(c!==d)return null;let g=JSON.parse(Buffer.from(i,"base64url").toString());return g.exp&&Date.now()/1e3>g.exp||g.sub!=="x402-access-pass"?null:g}catch{return null}}var Be="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",$e=6;function qe(e){let{payTo:t,network:n=Be,asset:r,facilitatorUrl:i,tiers:c,ratePerHour:d,secret:g,issuer:P="x402-access-pass",verbose:w=!1,description:k}=e,A=g??te.randomBytes(32);if(g||console.warn("[x402:access-pass] No secret provided \u2014 access passes will be invalidated on server restart. Set `secret` for production use."),!c&&!d)throw new Error("x402AccessPass: at least one of `tiers` or `ratePerHour` is required");let o=w?console.log.bind(console,"[x402:access-pass]"):()=>{},x=r?.decimals??$e,f=[];if(c){for(let[l,b]of Object.entries(c)){let a=Ie(l);if(!a){console.warn(`x402AccessPass: skipping tier "${l}" \u2014 unrecognized duration format (use 5m, 1h, 24h, 7d)`);continue}f.push({id:l,label:de(a),seconds:a,price:b,priceAtomic:V(parseFloat(b),x)})}f.sort((l,b)=>l.seconds-b.seconds)}let m=K({payTo:t,network:n,asset:r,facilitatorUrl:i}),y={tiers:f.length>0?f:void 0,ratePerHour:d||void 0,issuer:P},S=F(y);function u(l){if(!d)throw new Error("Custom durations not supported \u2014 no ratePerHour configured");let b=l/3600,a=(parseFloat(d)*b).toFixed(x>4?4:2);return{price:a,priceAtomic:V(parseFloat(a),x)}}function h(l){let b=l.query.tier,a=l.query.duration;if(b){let s=f.find(R=>R.id===b);if(s)return{tier:s.id,seconds:s.seconds,price:s.price,priceAtomic:s.priceAtomic,label:s.label}}if(a){let s=parseInt(a,10);if(s>0&&d){let R=u(s);return{tier:"custom",seconds:s,...R,label:de(s)}}}if(f.length>0){let s=f[0];return{tier:s.id,seconds:s.seconds,price:s.price,priceAtomic:s.priceAtomic,label:s.label}}return{tier:"custom",seconds:3600,...u(3600),label:"1 hour"}}return async(l,b,a)=>{try{let p=l.headers.authorization;if(p?.startsWith("Bearer ")){let T=De(p.slice(7),A);if(T)return o("Valid access pass:",T.tier,"| expires:",new Date(T.exp*1e3).toISOString()),l.accessPass={tier:T.tier,duration:T.duration,expiresAt:new Date(T.exp*1e3).toISOString(),payer:T.payer,network:T.network},a();o("Invalid or expired access pass token")}let s=l.headers["payment-signature"];if(s){o("Payment signature received, verifying for pass purchase...");let T=await m.verifyPayment(s);if(!T.isValid){o("Payment verification failed:",T.invalidReason),b.status(402).json({error:"Payment verification failed",reason:T.invalidReason});return}let v=await m.settlePayment(s);if(!v.success){o("Payment settlement failed:",v.errorReason),b.status(402).json({error:"Payment settlement failed",reason:v.errorReason});return}o("Payment settled:",v.transaction);let O=h(l),q=Math.floor(Date.now()/1e3),C={sub:"x402-access-pass",tier:O.tier,duration:O.seconds,iat:q,exp:q+O.seconds,payer:T.payer??"",network:n,iss:P},X=Ue(C,A);l.x402={transaction:v.transaction,payer:T.payer??"",network:n};let Z={success:!0,transaction:v.transaction,network:n,payer:T.payer??""};b.setHeader("PAYMENT-RESPONSE",F(Z)),b.setHeader("ACCESS-PASS",X),b.json({accessPass:{token:X,tier:O.tier,duration:O.label,durationSeconds:O.seconds,expiresAt:new Date((q+O.seconds)*1e3).toISOString(),usage:"Include on subsequent requests as: Authorization: Bearer <token>"},transaction:v.transaction,payer:T.payer});return}o("No access pass or payment, returning 402");let R=h(l),_=R.priceAtomic,E=`${l.protocol}://${l.get("host")}${l.originalUrl}`,M=await m.buildRequirements({amountAtomic:_,resourceUrl:E,description:k||`Access pass: ${R.label}`,mimeType:"application/json"}),$=m.encodeRequirements(M);b.setHeader("PAYMENT-REQUIRED",$),b.setHeader("X-ACCESS-PASS-TIERS",S),b.status(402).json({error:"Access pass required",message:"Purchase an access pass to unlock unlimited API access for a time window.",accepts:M.accepts,resource:M.resource,accessPass:{tiers:f.length>0?f:void 0,ratePerHour:d||void 0,usage:"Add ?tier=<id> or ?duration=<seconds> to your payment request to choose a pass duration."}})}catch(p){o("Access pass middleware error:",p),b.status(500).json({error:"Payment processing error",message:p instanceof Error?p.message:"Unknown error"})}}}import{createHmac as Fe,randomBytes as We}from"crypto";var ze=300;function Le(e){let t={unitSize:e.unitSize,ratePerUnit:e.ratePerUnit,minUsd:e.minUsd??.01,maxUsd:e.maxUsd??1/0,roundingMode:e.roundingMode??"ceil",decimals:e.decimals??6},{unitSize:n,ratePerUnit:r,minUsd:i,maxUsd:c,roundingMode:d,decimals:g}=t;if(n<=0)throw new Error("unitSize must be positive");if(r<=0)throw new Error("ratePerUnit must be positive");if(i<0)throw new Error("minUsd cannot be negative");if(c<i)throw new Error("maxUsd must be >= minUsd");let P=We(32);function w(o,x){let f=JSON.stringify({unitSize:n,ratePerUnit:r,minUsd:i,maxUsd:c===1/0?"none":c,roundingMode:d}),m=`${o}|${f}|${x}`;return Fe("sha256",P).update(m).digest("hex").slice(0,16)}function k(o){let x=o.length,f=x/n,m;switch(d){case"ceil":m=Math.ceil(f);break;case"floor":m=Math.floor(f);break;case"round":m=Math.round(f);break}x>0&&m===0&&(m=1);let y=m*r;y=Math.max(i,y),y=Math.min(c,y);let S=Math.pow(10,g),u=Math.floor(y*S).toString(),h=Math.floor(Date.now()/1e3),l=w(o,h),b=`${h}.${l}`;return{amountAtomic:u,usdAmount:y,quoteHash:b,units:m,inputLength:x}}function A(o,x){if(!x)return!1;let f=x.indexOf(".");if(f===-1)return!1;let m=parseInt(x.slice(0,f),10),y=x.slice(f+1);if(isNaN(m)||!y)return!1;let S=Math.floor(Date.now()/1e3)-m;if(S<0||S>ze)return!1;let u=w(o,m);if(y.length!==u.length)return!1;let h=0;for(let l=0;l<y.length;l++)h|=y.charCodeAt(l)^u.charCodeAt(l);return h===0}return{calculate:k,validateQuote:A,config:t}}function Ve(e){let t=e.ratePerUnit.toFixed(2),n=e.unitSize.toLocaleString();return`from $${t} per ${n} chars`}import{createHash as tt}from"crypto";var I={usesMaxCompletionTokens:!1,supportsTemperature:!0,supportsTopP:!0,supportsFrequencyPenalty:!0,supportsPresencePenalty:!0,supportsReasoningEffort:!1,supportsStreaming:!0,supportsSystemMessage:!0,supportsTools:!0,supportsStructuredOutput:!0},W={usesMaxCompletionTokens:!0,supportsTemperature:!1,supportsTopP:!1,supportsFrequencyPenalty:!1,supportsPresencePenalty:!1,supportsReasoningEffort:!1,supportsStreaming:!0,supportsSystemMessage:!0,supportsTools:!0,supportsStructuredOutput:!0},B={usesMaxCompletionTokens:!0,supportsTemperature:!1,supportsTopP:!1,supportsFrequencyPenalty:!1,supportsPresencePenalty:!1,supportsReasoningEffort:!0,supportsStreaming:!0,supportsSystemMessage:!0,supportsTools:!0,supportsStructuredOutput:!0},le={...B,supportsStreaming:!1},U=[{id:"gpt-4o-mini",displayName:"GPT-4o Mini",family:"gpt-4o",tier:"fast",capabilityRank:3,modalities:["text","vision"],apiType:"chat",pricing:{input:.15,output:.6,cached:.075},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:16384,parameters:I,deprecated:!1,description:"Fast, affordable small model with vision support"},{id:"gpt-4.1-nano",displayName:"GPT-4.1 Nano",family:"gpt-4.1",tier:"fast",capabilityRank:2,modalities:["text"],apiType:"chat",pricing:{input:.1,output:.4,cached:.025},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:32768,parameters:I,deprecated:!1,description:"Smallest 4.1 model, very fast and cheap"},{id:"gpt-4.1-mini",displayName:"GPT-4.1 Mini",family:"gpt-4.1",tier:"fast",capabilityRank:4,modalities:["text"],apiType:"chat",pricing:{input:.4,output:1.6,cached:.1},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:32768,parameters:I,deprecated:!1,description:"Balanced 4.1 model, good price/performance"},{id:"gpt-5-nano",displayName:"GPT-5 Nano",family:"gpt-5",tier:"fast",capabilityRank:1,modalities:["text"],apiType:"chat",pricing:{input:.05,output:.4,cached:.005},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:16384,parameters:W,deprecated:!1,description:"Cheapest GPT-5 variant, extremely fast"},{id:"gpt-5-mini",displayName:"GPT-5 Mini",family:"gpt-5",tier:"fast",capabilityRank:5,modalities:["text"],apiType:"chat",pricing:{input:.25,output:2,cached:.025},contextWindow:128e3,defaultMaxOutput:8192,maxOutputTokens:32768,parameters:W,deprecated:!1,description:"Small but capable GPT-5, great value"},{id:"gpt-4o",displayName:"GPT-4o",family:"gpt-4o",tier:"standard",capabilityRank:5,modalities:["text","vision"],apiType:"chat",pricing:{input:2.5,output:10,cached:1.25},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:16384,parameters:I,deprecated:!1,description:"Flagship multimodal model with vision"},{id:"gpt-4.1",displayName:"GPT-4.1",family:"gpt-4.1",tier:"standard",capabilityRank:6,modalities:["text"],apiType:"chat",pricing:{input:2,output:8,cached:.5},contextWindow:1e6,defaultMaxOutput:8192,maxOutputTokens:32768,parameters:I,deprecated:!1,description:"Long context specialist, 1M token window"},{id:"gpt-5",displayName:"GPT-5",family:"gpt-5",tier:"standard",capabilityRank:7,modalities:["text"],apiType:"chat",pricing:{input:1.25,output:10,cached:.125},contextWindow:128e3,defaultMaxOutput:8192,maxOutputTokens:32768,parameters:W,deprecated:!1,description:"Base GPT-5, excellent all-around"},{id:"gpt-5.1",displayName:"GPT-5.1",family:"gpt-5",tier:"standard",capabilityRank:8,modalities:["text"],apiType:"chat",pricing:{input:1.25,output:10,cached:.125},contextWindow:128e3,defaultMaxOutput:8192,maxOutputTokens:32768,parameters:W,deprecated:!1,description:"Improved GPT-5 with better instruction following"},{id:"gpt-5.2",displayName:"GPT-5.2",family:"gpt-5",tier:"standard",capabilityRank:9,modalities:["text"],apiType:"chat",pricing:{input:1.75,output:14,cached:.175},contextWindow:128e3,defaultMaxOutput:8192,maxOutputTokens:32768,parameters:W,deprecated:!1,description:"Latest GPT-5, most capable standard model"},{id:"o1-mini",displayName:"o1 Mini",family:"o1",tier:"reasoning",capabilityRank:3,modalities:["text"],apiType:"chat",pricing:{input:1.1,output:4.4,cached:.55},contextWindow:128e3,defaultMaxOutput:16384,maxOutputTokens:65536,parameters:B,deprecated:!1,description:"Fast reasoning model, good for math/code"},{id:"o3-mini",displayName:"o3 Mini",family:"o3",tier:"reasoning",capabilityRank:4,modalities:["text"],apiType:"chat",pricing:{input:1.1,output:4.4,cached:.55},contextWindow:128e3,defaultMaxOutput:16384,maxOutputTokens:65536,parameters:B,deprecated:!1,description:"Improved mini reasoner with better efficiency"},{id:"o4-mini",displayName:"o4 Mini",family:"o4",tier:"reasoning",capabilityRank:5,modalities:["text"],apiType:"chat",pricing:{input:1.1,output:4.4,cached:.275},contextWindow:128e3,defaultMaxOutput:16384,maxOutputTokens:65536,parameters:B,deprecated:!1,description:"Latest mini reasoner, best reasoning per dollar"},{id:"o3",displayName:"o3",family:"o3",tier:"reasoning",capabilityRank:7,modalities:["text"],apiType:"chat",pricing:{input:2,output:8,cached:.5},contextWindow:2e5,defaultMaxOutput:32768,maxOutputTokens:1e5,parameters:B,deprecated:!1,description:"Full o3 reasoning model, excellent for complex problems"},{id:"o1",displayName:"o1",family:"o1",tier:"reasoning",capabilityRank:8,modalities:["text"],apiType:"chat",pricing:{input:15,output:60,cached:7.5},contextWindow:2e5,defaultMaxOutput:32768,maxOutputTokens:1e5,parameters:B,deprecated:!1,description:"Original full reasoning model, very capable"},{id:"gpt-5-pro",displayName:"GPT-5 Pro",family:"gpt-5",tier:"premium",capabilityRank:7,modalities:["text"],apiType:"chat",pricing:{input:15,output:120},contextWindow:128e3,defaultMaxOutput:16384,maxOutputTokens:32768,parameters:W,deprecated:!1,description:"Enhanced GPT-5 for demanding tasks"},{id:"gpt-5.2-pro",displayName:"GPT-5.2 Pro",family:"gpt-5",tier:"premium",capabilityRank:8,modalities:["text"],apiType:"chat",pricing:{input:21,output:168},contextWindow:128e3,defaultMaxOutput:16384,maxOutputTokens:32768,parameters:W,deprecated:!1,description:"Most capable standard model available"},{id:"o3-pro",displayName:"o3 Pro",family:"o3",tier:"premium",capabilityRank:9,modalities:["text"],apiType:"chat",pricing:{input:20,output:80},contextWindow:2e5,defaultMaxOutput:32768,maxOutputTokens:1e5,parameters:le,deprecated:!1,description:"Premium o3 with extended thinking time"},{id:"o1-pro",displayName:"o1 Pro",family:"o1",tier:"premium",capabilityRank:10,modalities:["text"],apiType:"chat",pricing:{input:150,output:600},contextWindow:2e5,defaultMaxOutput:32768,maxOutputTokens:1e5,parameters:le,deprecated:!1,description:"Most capable reasoning model, extended compute"},{id:"o3-deep-research",displayName:"o3 Deep Research",family:"o3",tier:"specialized",capabilityRank:8,modalities:["text"],apiType:"responses",pricing:{input:10,output:40,cached:2.5},contextWindow:2e5,defaultMaxOutput:32768,maxOutputTokens:1e5,parameters:{...B,supportsStreaming:!1},deprecated:!1,description:"Extended research sessions with web access"},{id:"o4-mini-deep-research",displayName:"o4 Mini Deep Research",family:"o4",tier:"specialized",capabilityRank:6,modalities:["text"],apiType:"responses",pricing:{input:2,output:8,cached:.5},contextWindow:128e3,defaultMaxOutput:16384,maxOutputTokens:65536,parameters:{...B,supportsStreaming:!1},deprecated:!1,description:"Affordable deep research with o4 mini"},{id:"computer-use-preview",displayName:"Computer Use Preview",family:"computer-use",tier:"specialized",capabilityRank:5,modalities:["text","vision"],apiType:"responses",pricing:{input:3,output:12},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:16384,parameters:{...I,supportsReasoningEffort:!1},deprecated:!1,description:"Can control computer interfaces via screenshots"},{id:"gpt-realtime",displayName:"GPT Realtime",family:"gpt-realtime",tier:"specialized",capabilityRank:7,modalities:["text","audio","realtime"],apiType:"chat",pricing:{input:4,output:16,cached:.4},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:4096,parameters:{...I,supportsReasoningEffort:!1},deprecated:!1,description:"Real-time audio conversation model"},{id:"gpt-realtime-mini",displayName:"GPT Realtime Mini",family:"gpt-realtime",tier:"specialized",capabilityRank:4,modalities:["text","audio","realtime"],apiType:"chat",pricing:{input:.6,output:2.4,cached:.06},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:4096,parameters:{...I,supportsReasoningEffort:!1},deprecated:!1,description:"Affordable real-time audio model"},{id:"gpt-4o-2024-05-13",displayName:"GPT-4o (May 2024)",family:"gpt-4o",tier:"standard",capabilityRank:4,modalities:["text","vision"],apiType:"chat",pricing:{input:5,output:15},contextWindow:128e3,defaultMaxOutput:4096,maxOutputTokens:4096,parameters:I,deprecated:!0,description:"Original GPT-4o snapshot, use gpt-4o instead"}],re=new Map(U.map(e=>[e.id,e]));function ne(e){let t=re.get(e);if(!t)throw new Error(`Unknown model: ${e}. Use getAvailableModelIds() to see valid options.`);return t}function je(e){return re.get(e)}function Ge(e){return re.has(e)}function He(){return U.map(e=>e.id)}function Ke(e){return U.filter(t=>t.tier===e&&!t.deprecated).sort((t,n)=>t.capabilityRank-n.capabilityRank)}function Xe(e){return U.filter(t=>t.family===e).sort((t,n)=>t.capabilityRank-n.capabilityRank)}function Je(){let e={fast:1,standard:2,reasoning:3,premium:4,specialized:5};return U.filter(t=>!t.deprecated).sort((t,n)=>{let r=e[t.tier]-e[n.tier];return r!==0?r:t.capabilityRank-n.capabilityRank})}function Ye(){return U.filter(e=>!e.deprecated&&e.modalities.includes("text")&&!e.modalities.includes("realtime")&&e.apiType!=="responses").sort((e,t)=>{let n={fast:1,standard:2,reasoning:3,premium:4,specialized:5},r=n[e.tier]-n[t.tier];return r!==0?r:e.capabilityRank-t.capabilityRank})}function Qe(e="fast"){let t={fast:1,standard:2,reasoning:3,premium:4,specialized:5},n=t[e];return U.filter(i=>!i.deprecated&&t[i.tier]>=n).sort((i,c)=>i.pricing.input-c.pricing.input)[0]}function Ze(e,t,n,r=!1){let i=ne(e),c=r&&i.pricing.cached?t/1e6*i.pricing.cached:t/1e6*i.pricing.input,d=n/1e6*i.pricing.output;return c+d}function et(e){let t=ne(e);return`$${t.pricing.input.toFixed(2)} in / $${t.pricing.output.toFixed(2)} out per 1M tokens`}var ie=Object.fromEntries(U.map(e=>[e.id,{input:e.pricing.input,output:e.pricing.output,cached:e.pricing.cached,maxTokens:e.defaultMaxOutput,tier:e.tier}]));var Q=null;async function rt(){if(Q)return Q;try{return Q=await import("tiktoken"),Q}catch{throw new Error('Token pricing requires the "tiktoken" package. Install with: npm install tiktoken')}}var D=ie,z="gpt-4o-mini";async function nt(e){let t=await rt();try{return t.encoding_for_model(e)}catch{return t.get_encoding("cl100k_base")}}async function se(e,t=z){let n=await nt(t);try{return n.encode(e).length}finally{n.free()}}function me(e,t,n,r){let i=JSON.stringify({model:t,rate:n,tokens:r});return tt("sha256").update(e+i).digest("hex").slice(0,16)}function it(e={}){let t=e.model??z,n=D[t],r={input:e.inputRate??n?.input??D[z].input,output:e.outputRate??n?.output??D[z].output,maxTokens:e.maxTokens??n?.maxTokens??4096,tier:e.tier??n?.tier??"custom"},i=e.tokenizer,c={model:t,inputRate:r.input,outputRate:r.output,maxTokens:r.maxTokens,tier:r.tier,tokenizer:i??(o=>se(o,t)),minUsd:e.minUsd??.001,maxUsd:e.maxUsd??50,decimals:e.decimals??6},{minUsd:d,maxUsd:g,decimals:P}=c;async function w(o){return i?i(o):se(o,t)}async function k(o,x){let f=x?`${x}
|
|
2056
285
|
|
|
2057
|
-
|
|
2058
|
-
import { SPONSORED_ACCESS_EXTENSION_KEY } from "@dexterai/x402-ads-types";
|
|
2059
|
-
export {
|
|
2060
|
-
BASE_MAINNET_NETWORK,
|
|
2061
|
-
DEXTER_FACILITATOR_URL,
|
|
2062
|
-
FacilitatorClient,
|
|
2063
|
-
MODEL_PRICING,
|
|
2064
|
-
MODEL_PRICING_MAP,
|
|
2065
|
-
MODEL_REGISTRY,
|
|
2066
|
-
SOLANA_MAINNET_NETWORK,
|
|
2067
|
-
SPONSORED_ACCESS_EXTENSION_KEY,
|
|
2068
|
-
USDC_BASE,
|
|
2069
|
-
USDC_MINT,
|
|
2070
|
-
countTokens,
|
|
2071
|
-
createDynamicPricing,
|
|
2072
|
-
createTokenPricing,
|
|
2073
|
-
createX402Server,
|
|
2074
|
-
escapeHtml,
|
|
2075
|
-
estimateCost,
|
|
2076
|
-
findModel,
|
|
2077
|
-
formatModelPricing,
|
|
2078
|
-
formatPricing,
|
|
2079
|
-
formatTokenPricing,
|
|
2080
|
-
getActiveModels,
|
|
2081
|
-
getAvailableModelIds,
|
|
2082
|
-
getAvailableModels,
|
|
2083
|
-
getCheapestModel,
|
|
2084
|
-
getModel,
|
|
2085
|
-
getModelsByFamily,
|
|
2086
|
-
getModelsByTier,
|
|
2087
|
-
getTextModels,
|
|
2088
|
-
isValidModel,
|
|
2089
|
-
isValidModelId,
|
|
2090
|
-
stripePayTo,
|
|
2091
|
-
x402AccessPass,
|
|
2092
|
-
x402BrowserSupport,
|
|
2093
|
-
x402Middleware
|
|
2094
|
-
};
|
|
286
|
+
${o}`:o,m=await w(f),y=m/1e6*r.input;y=Math.max(y,d),y=Math.min(y,g);let S=Math.pow(10,P),u=Math.floor(y*S).toString(),h=me(o,t,r.input,m);return{amountAtomic:u,usdAmount:y,inputTokens:m,model:t,tier:r.tier,inputRatePerMillion:r.input,outputRatePerMillion:r.output,maxOutputTokens:r.maxTokens,quoteHash:h}}async function A(o,x){if(!x)return!1;let f=await w(o);return me(o,t,r.input,f)===x}return{calculate:k,validateQuote:A,countTokens:w,config:c,modelInfo:r}}function st(){return Object.entries(D).map(([e,t])=>({model:e,inputRate:t.input,outputRate:t.output,maxTokens:t.maxTokens,tier:t.tier})).sort((e,t)=>{let n={fast:0,standard:1,reasoning:2,premium:3},r=n[e.tier]-n[t.tier];return r!==0?r:e.inputRate-t.inputRate})}function ot(e){return e in D}function at(e=z){let t=D[e]??D[z],n=D[e]?e:z;return`$${t.input.toFixed(2)} per 1M tokens (${n})`}import{SPONSORED_ACCESS_EXTENSION_KEY as jt}from"@dexterai/x402-ads-types";export{oe as BASE_MAINNET_NETWORK,L as DEXTER_FACILITATOR_URL,H as FacilitatorClient,D as MODEL_PRICING,ie as MODEL_PRICING_MAP,U as MODEL_REGISTRY,Y as SOLANA_MAINNET_NETWORK,jt as SPONSORED_ACCESS_EXTENSION_KEY,fe as USDC_BASE,ee as USDC_MINT,se as countTokens,Le as createDynamicPricing,it as createTokenPricing,K as createX402Server,N as escapeHtml,Ze as estimateCost,je as findModel,et as formatModelPricing,Ve as formatPricing,at as formatTokenPricing,Je as getActiveModels,He as getAvailableModelIds,st as getAvailableModels,Qe as getCheapestModel,ne as getModel,Xe as getModelsByFamily,Ke as getModelsByTier,Ye as getTextModels,ot as isValidModel,Ge as isValidModelId,we as stripePayTo,qe as x402AccessPass,Oe as x402BrowserSupport,Ae as x402Middleware};
|