@dexterai/x402 1.9.4 → 2.0.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 +0 -1
- package/dist/adapters/index.d.cts +2 -4
- package/dist/adapters/index.d.ts +2 -4
- package/dist/adapters/index.js +0 -1
- package/dist/client/index.cjs +192 -6
- package/dist/client/index.d.cts +199 -35
- package/dist/client/index.d.ts +199 -35
- package/dist/client/index.js +190 -6
- package/dist/react/index.cjs +26 -4
- package/dist/react/index.d.cts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +26 -4
- package/dist/server/index.cjs +0 -1
- package/dist/server/index.js +0 -1
- package/dist/{sponsored-access-Br6YPA-m.d.cts → sponsored-access-BgEDLg_H.d.cts} +12 -1
- package/dist/{sponsored-access-D1_mINs4.d.ts → sponsored-access-DjLEKhOV.d.ts} +12 -1
- package/dist/types-D1TGACsL.d.ts +245 -0
- package/dist/types-DWhpiOBD.d.cts +245 -0
- package/dist/utils/index.cjs +0 -1
- package/dist/utils/index.js +0 -1
- package/package.json +1 -1
- package/dist/adapters/index.cjs.map +0 -1
- package/dist/adapters/index.js.map +0 -1
- package/dist/client/index.cjs.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.js.map +0 -1
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/solana-BcOfK6Eq.d.cts +0 -132
- package/dist/solana-Cxr5byPa.d.ts +0 -132
- package/dist/types-BIHhO2-I.d.ts +0 -123
- package/dist/types-CfKflCZO.d.cts +0 -123
- package/dist/utils/index.cjs.map +0 -1
- package/dist/utils/index.js.map +0 -1
package/dist/client/index.d.ts
CHANGED
|
@@ -1,32 +1,11 @@
|
|
|
1
|
-
export { P as PaymentReceipt, a as X402Client, X as X402ClientConfig, c as createX402Client, f as fireImpressionBeacon, g as getPaymentReceipt, d as getSponsoredAccessInfo, b as getSponsoredRecommendations } from '../sponsored-access-
|
|
2
|
-
import { A as AccessPassClientConfig } from '../types-CjLMR7qs.js';
|
|
1
|
+
export { P as PaymentReceipt, a as X402Client, X as X402ClientConfig, c as createX402Client, f as fireImpressionBeacon, g as getPaymentReceipt, d as getSponsoredAccessInfo, b as getSponsoredRecommendations } from '../sponsored-access-DjLEKhOV.js';
|
|
2
|
+
import { A as AccessPassClientConfig, P as PaymentAccept } from '../types-CjLMR7qs.js';
|
|
3
3
|
export { b as AccessPassInfo, a as AccessPassTier, D as DEXTER_FACILITATOR_URL, U as USDC_MINT, X as X402Error } from '../types-CjLMR7qs.js';
|
|
4
|
-
import {
|
|
5
|
-
import { E as EvmWallet } from '../
|
|
6
|
-
export { B as BASE_MAINNET,
|
|
7
|
-
export { C as ChainAdapter, W as WalletSet } from '../types-BIHhO2-I.js';
|
|
4
|
+
import { Keypair } from '@solana/web3.js';
|
|
5
|
+
import { S as SolanaWallet, E as EvmWallet } from '../types-D1TGACsL.js';
|
|
6
|
+
export { B as BASE_MAINNET, C as ChainAdapter, b as SOLANA_MAINNET, W as WalletSet, a as createEvmAdapter, c as createSolanaAdapter } from '../types-D1TGACsL.js';
|
|
8
7
|
export { SponsoredAccessSettlementInfo, SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
9
8
|
|
|
10
|
-
/**
|
|
11
|
-
* Simple Fetch Wrapper for Node.js
|
|
12
|
-
*
|
|
13
|
-
* The easiest way to make x402 payments from Node.js scripts.
|
|
14
|
-
* Just provide a private key and it handles everything automatically.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* import { wrapFetch } from '@dexterai/x402/client';
|
|
19
|
-
*
|
|
20
|
-
* const x402Fetch = wrapFetch(fetch, {
|
|
21
|
-
* walletPrivateKey: process.env.SOLANA_PRIVATE_KEY!,
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* // Make a paid request - payment happens automatically
|
|
25
|
-
* const response = await x402Fetch('https://api.example.com/protected');
|
|
26
|
-
* const data = await response.json();
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
9
|
/**
|
|
31
10
|
* Options for wrapFetch
|
|
32
11
|
*/
|
|
@@ -81,6 +60,11 @@ interface WrapFetchOptions {
|
|
|
81
60
|
* ```
|
|
82
61
|
*/
|
|
83
62
|
accessPass?: AccessPassClientConfig;
|
|
63
|
+
/**
|
|
64
|
+
* Called before signing a payment. Return true to proceed, false to reject.
|
|
65
|
+
* Used internally by Budget Accounts — can also be set directly.
|
|
66
|
+
*/
|
|
67
|
+
onPaymentRequired?: (requirements: PaymentAccept) => boolean | Promise<boolean>;
|
|
84
68
|
}
|
|
85
69
|
/**
|
|
86
70
|
* Wrap fetch with automatic x402 payment handling
|
|
@@ -138,15 +122,9 @@ declare function wrapFetch(fetchImpl: typeof globalThis.fetch, options: WrapFetc
|
|
|
138
122
|
/** Symbol key for the underlying Keypair — prevents accidental exposure via console.log or JSON.stringify */
|
|
139
123
|
declare const KEYPAIR_SYMBOL: unique symbol;
|
|
140
124
|
/**
|
|
141
|
-
* Keypair wallet interface
|
|
125
|
+
* Keypair wallet interface — extends SolanaWallet with safe Keypair access.
|
|
142
126
|
*/
|
|
143
|
-
interface KeypairWallet {
|
|
144
|
-
/** Public key with toBase58() method */
|
|
145
|
-
publicKey: {
|
|
146
|
-
toBase58(): string;
|
|
147
|
-
};
|
|
148
|
-
/** Sign a transaction */
|
|
149
|
-
signTransaction<T extends Transaction | VersionedTransaction>(tx: T): Promise<T>;
|
|
127
|
+
interface KeypairWallet extends SolanaWallet {
|
|
150
128
|
/**
|
|
151
129
|
* Get the underlying Keypair. Accessed via Symbol to prevent accidental
|
|
152
130
|
* exposure through console.log, JSON.stringify, or Object.keys.
|
|
@@ -259,4 +237,190 @@ declare function createEvmKeypairWallet(privateKey: string): Promise<EvmWallet>;
|
|
|
259
237
|
*/
|
|
260
238
|
declare function isEvmKeypairWallet(wallet: unknown): wallet is EvmWallet;
|
|
261
239
|
|
|
262
|
-
|
|
240
|
+
/**
|
|
241
|
+
* API Discovery — Find x402 Paid APIs
|
|
242
|
+
*
|
|
243
|
+
* Search the Dexter marketplace for x402-enabled APIs. Discover endpoints
|
|
244
|
+
* by category, price range, network, and quality score — then pay for them
|
|
245
|
+
* with the same SDK.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* import { searchAPIs } from '@dexterai/x402/client';
|
|
250
|
+
*
|
|
251
|
+
* const results = await searchAPIs({ query: 'sentiment analysis', maxPrice: 0.10 });
|
|
252
|
+
* for (const api of results) {
|
|
253
|
+
* console.log(`${api.name}: ${api.price} — ${api.description}`);
|
|
254
|
+
* }
|
|
255
|
+
*
|
|
256
|
+
* // Then call one with wrapFetch:
|
|
257
|
+
* const response = await x402Fetch(results[0].url);
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
/**
|
|
261
|
+
* Search options for discovering x402 APIs
|
|
262
|
+
*/
|
|
263
|
+
interface SearchAPIsOptions {
|
|
264
|
+
/** Search query (e.g., 'sentiment analysis', 'token price', 'image generation') */
|
|
265
|
+
query?: string;
|
|
266
|
+
/** Filter by category (e.g., 'defi', 'ai', 'data', 'social') */
|
|
267
|
+
category?: string;
|
|
268
|
+
/** Filter by payment network (e.g., 'solana', 'base', 'polygon') */
|
|
269
|
+
network?: string;
|
|
270
|
+
/** Maximum price per call in USDC */
|
|
271
|
+
maxPrice?: number;
|
|
272
|
+
/** Only return verified endpoints (quality score 75+) */
|
|
273
|
+
verifiedOnly?: boolean;
|
|
274
|
+
/** Sort order */
|
|
275
|
+
sort?: 'marketplace' | 'relevance' | 'quality_score' | 'settlements' | 'volume' | 'recent';
|
|
276
|
+
/** Maximum results to return (default 20, max 50) */
|
|
277
|
+
limit?: number;
|
|
278
|
+
/** Marketplace API URL (default: Dexter marketplace) */
|
|
279
|
+
marketplaceUrl?: string;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A discovered x402 API endpoint
|
|
283
|
+
*/
|
|
284
|
+
interface DiscoveredAPI {
|
|
285
|
+
/** API name */
|
|
286
|
+
name: string;
|
|
287
|
+
/** Full resource URL — pass directly to wrapFetch or createX402Client.fetch */
|
|
288
|
+
url: string;
|
|
289
|
+
/** HTTP method */
|
|
290
|
+
method: string;
|
|
291
|
+
/** Price per call (formatted, e.g., '$0.05') */
|
|
292
|
+
price: string;
|
|
293
|
+
/** Price per call in USDC (raw number, null if free) */
|
|
294
|
+
priceUsdc: number | null;
|
|
295
|
+
/** Payment network */
|
|
296
|
+
network: string | null;
|
|
297
|
+
/** Human-readable description */
|
|
298
|
+
description: string;
|
|
299
|
+
/** Category (e.g., 'defi', 'ai', 'data') */
|
|
300
|
+
category: string;
|
|
301
|
+
/** Quality score (0-100, null if unscored) */
|
|
302
|
+
qualityScore: number | null;
|
|
303
|
+
/** Whether the endpoint has been verified */
|
|
304
|
+
verified: boolean;
|
|
305
|
+
/** Total number of settlements (calls) */
|
|
306
|
+
totalCalls: number;
|
|
307
|
+
/** Total volume in USDC (formatted, e.g., '$1,234.56') */
|
|
308
|
+
totalVolume: string | null;
|
|
309
|
+
/** Seller name */
|
|
310
|
+
seller: string | null;
|
|
311
|
+
/** Seller reputation score */
|
|
312
|
+
sellerReputation: number | null;
|
|
313
|
+
/** Whether authentication is required beyond payment */
|
|
314
|
+
authRequired: boolean;
|
|
315
|
+
/** Last time someone called this API */
|
|
316
|
+
lastActive: string | null;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Search the Dexter marketplace for x402 paid APIs.
|
|
320
|
+
*
|
|
321
|
+
* Returns a list of discovered endpoints that can be called directly
|
|
322
|
+
* with `wrapFetch` or `createX402Client.fetch`.
|
|
323
|
+
*
|
|
324
|
+
* @example Find AI APIs under $0.10
|
|
325
|
+
* ```typescript
|
|
326
|
+
* const apis = await searchAPIs({ query: 'ai', maxPrice: 0.10 });
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
329
|
+
* @example Browse all verified DeFi tools
|
|
330
|
+
* ```typescript
|
|
331
|
+
* const apis = await searchAPIs({ category: 'defi', verifiedOnly: true });
|
|
332
|
+
* ```
|
|
333
|
+
*
|
|
334
|
+
* @example Find cheapest APIs on Solana
|
|
335
|
+
* ```typescript
|
|
336
|
+
* const apis = await searchAPIs({ network: 'solana', sort: 'quality_score' });
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
declare function searchAPIs(options?: SearchAPIsOptions): Promise<DiscoveredAPI[]>;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Budget Account — Autonomous Agent Spending Controls
|
|
343
|
+
*
|
|
344
|
+
* Wraps the x402 client with a spending limit, per-request cap,
|
|
345
|
+
* per-hour rate limit, and optional domain allowlist. Tracks cumulative
|
|
346
|
+
* spend and exposes remaining budget. When the budget is exhausted,
|
|
347
|
+
* requests throw instead of paying.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* import { createBudgetAccount } from '@dexterai/x402/client';
|
|
352
|
+
*
|
|
353
|
+
* const agent = createBudgetAccount({
|
|
354
|
+
* walletPrivateKey: process.env.SOLANA_PRIVATE_KEY,
|
|
355
|
+
* budget: {
|
|
356
|
+
* total: '50.00', // $50 total budget
|
|
357
|
+
* perRequest: '1.00', // max $1 per request
|
|
358
|
+
* perHour: '10.00', // max $10/hour
|
|
359
|
+
* },
|
|
360
|
+
* allowedDomains: ['api.example.com', 'data.example.com'],
|
|
361
|
+
* });
|
|
362
|
+
*
|
|
363
|
+
* const response = await agent.fetch('https://api.example.com/data');
|
|
364
|
+
* console.log(agent.spent); // '$0.05'
|
|
365
|
+
* console.log(agent.remaining); // '$49.95'
|
|
366
|
+
* console.log(agent.payments); // 1
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
|
|
370
|
+
/** Budget configuration */
|
|
371
|
+
interface BudgetConfig {
|
|
372
|
+
/** Total spending limit in USD (e.g., '50.00') */
|
|
373
|
+
total: string;
|
|
374
|
+
/** Maximum amount per single request in USD (e.g., '1.00'). Optional. */
|
|
375
|
+
perRequest?: string;
|
|
376
|
+
/** Maximum spend per hour in USD (e.g., '10.00'). Optional. */
|
|
377
|
+
perHour?: string;
|
|
378
|
+
}
|
|
379
|
+
/** Budget Account configuration — extends WrapFetchOptions with spending controls */
|
|
380
|
+
interface BudgetAccountConfig extends WrapFetchOptions {
|
|
381
|
+
/** Spending limits */
|
|
382
|
+
budget: BudgetConfig;
|
|
383
|
+
/** Restrict payments to these domains only. If omitted, all domains allowed. */
|
|
384
|
+
allowedDomains?: string[];
|
|
385
|
+
}
|
|
386
|
+
/** A payment record in the spend ledger */
|
|
387
|
+
interface PaymentRecord {
|
|
388
|
+
/** Amount paid in USD */
|
|
389
|
+
amount: number;
|
|
390
|
+
/** Domain that was paid */
|
|
391
|
+
domain: string;
|
|
392
|
+
/** CAIP-2 network used */
|
|
393
|
+
network: string;
|
|
394
|
+
/** Timestamp (ms) */
|
|
395
|
+
timestamp: number;
|
|
396
|
+
}
|
|
397
|
+
/** Budget Account — fetch with spending controls */
|
|
398
|
+
interface BudgetAccount {
|
|
399
|
+
/** Payment-aware fetch with budget enforcement */
|
|
400
|
+
fetch: typeof globalThis.fetch;
|
|
401
|
+
/** Total amount spent (formatted, e.g., '$12.34') */
|
|
402
|
+
readonly spent: string;
|
|
403
|
+
/** Remaining budget (formatted, e.g., '$37.66') */
|
|
404
|
+
readonly remaining: string;
|
|
405
|
+
/** Number of payments made */
|
|
406
|
+
readonly payments: number;
|
|
407
|
+
/** Total spent as a raw number */
|
|
408
|
+
readonly spentAmount: number;
|
|
409
|
+
/** Remaining budget as a raw number */
|
|
410
|
+
readonly remainingAmount: number;
|
|
411
|
+
/** Full payment history */
|
|
412
|
+
readonly ledger: readonly PaymentRecord[];
|
|
413
|
+
/** Spend in the last hour */
|
|
414
|
+
readonly hourlySpend: number;
|
|
415
|
+
/** Reset the budget (clears all spend history) */
|
|
416
|
+
reset: () => void;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Create a budget-controlled fetch wrapper for autonomous agents.
|
|
420
|
+
*
|
|
421
|
+
* Enforces total spend limit, per-request cap, hourly rate limit,
|
|
422
|
+
* and domain allowlist. Every payment is tracked in an in-memory ledger.
|
|
423
|
+
*/
|
|
424
|
+
declare function createBudgetAccount(config: BudgetAccountConfig): BudgetAccount;
|
|
425
|
+
|
|
426
|
+
export { AccessPassClientConfig, type BudgetAccount, type BudgetAccountConfig, type BudgetConfig, type DiscoveredAPI, KEYPAIR_SYMBOL, type KeypairWallet, type PaymentRecord, type SearchAPIsOptions, type WrapFetchOptions, createBudgetAccount, createEvmKeypairWallet, createKeypairWallet, isEvmKeypairWallet, isKeypairWallet, searchAPIs, wrapFetch };
|
package/dist/client/index.js
CHANGED
|
@@ -629,10 +629,33 @@ function createX402Client(config) {
|
|
|
629
629
|
fetch: customFetch = globalThis.fetch,
|
|
630
630
|
verbose = false,
|
|
631
631
|
accessPass: accessPassConfig,
|
|
632
|
-
onPaymentRequired
|
|
632
|
+
onPaymentRequired,
|
|
633
|
+
maxRetries = 0,
|
|
634
|
+
retryDelayMs = 500
|
|
633
635
|
} = config;
|
|
634
636
|
const log = verbose ? console.log.bind(console, "[x402]") : () => {
|
|
635
637
|
};
|
|
638
|
+
async function fetchWithRetry(input, init) {
|
|
639
|
+
let lastError;
|
|
640
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
641
|
+
try {
|
|
642
|
+
const response = await customFetch(input, init);
|
|
643
|
+
if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
|
|
644
|
+
log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
|
|
645
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
return response;
|
|
649
|
+
} catch (err) {
|
|
650
|
+
lastError = err;
|
|
651
|
+
if (attempt < maxRetries) {
|
|
652
|
+
log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
|
|
653
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
throw lastError;
|
|
658
|
+
}
|
|
636
659
|
const passCache = /* @__PURE__ */ new Map();
|
|
637
660
|
function getCachedPass(url) {
|
|
638
661
|
try {
|
|
@@ -832,7 +855,7 @@ function createX402Client(config) {
|
|
|
832
855
|
}
|
|
833
856
|
}
|
|
834
857
|
}
|
|
835
|
-
const response = await
|
|
858
|
+
const response = await fetchWithRetry(input, init);
|
|
836
859
|
if (response.status !== 402) {
|
|
837
860
|
return response;
|
|
838
861
|
}
|
|
@@ -967,7 +990,7 @@ function createX402Client(config) {
|
|
|
967
990
|
};
|
|
968
991
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
969
992
|
log("Retrying request with payment...");
|
|
970
|
-
const retryResponse = await
|
|
993
|
+
const retryResponse = await fetchWithRetry(input, {
|
|
971
994
|
...init,
|
|
972
995
|
headers: {
|
|
973
996
|
...init?.headers || {},
|
|
@@ -1111,7 +1134,8 @@ function wrapFetch(fetchImpl, options) {
|
|
|
1111
1134
|
rpcUrls,
|
|
1112
1135
|
maxAmountAtomic,
|
|
1113
1136
|
verbose,
|
|
1114
|
-
accessPass
|
|
1137
|
+
accessPass,
|
|
1138
|
+
onPaymentRequired
|
|
1115
1139
|
} = options;
|
|
1116
1140
|
if (!walletPrivateKey && !evmPrivateKey) {
|
|
1117
1141
|
throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");
|
|
@@ -1144,7 +1168,8 @@ function wrapFetch(fetchImpl, options) {
|
|
|
1144
1168
|
maxAmountAtomic,
|
|
1145
1169
|
fetch: fetchImpl,
|
|
1146
1170
|
verbose,
|
|
1147
|
-
accessPass
|
|
1171
|
+
accessPass,
|
|
1172
|
+
onPaymentRequired
|
|
1148
1173
|
};
|
|
1149
1174
|
const client = createX402Client(clientConfig);
|
|
1150
1175
|
const clientFetch = client.fetch.bind(client);
|
|
@@ -1157,6 +1182,164 @@ function wrapFetch(fetchImpl, options) {
|
|
|
1157
1182
|
return clientFetch;
|
|
1158
1183
|
}
|
|
1159
1184
|
|
|
1185
|
+
// src/client/discovery.ts
|
|
1186
|
+
var DEFAULT_MARKETPLACE = "https://x402.dexter.cash/api/facilitator/marketplace/resources";
|
|
1187
|
+
async function searchAPIs(options = {}) {
|
|
1188
|
+
const {
|
|
1189
|
+
query,
|
|
1190
|
+
category,
|
|
1191
|
+
network,
|
|
1192
|
+
maxPrice,
|
|
1193
|
+
verifiedOnly,
|
|
1194
|
+
sort = "marketplace",
|
|
1195
|
+
limit = 20,
|
|
1196
|
+
marketplaceUrl = DEFAULT_MARKETPLACE
|
|
1197
|
+
} = options;
|
|
1198
|
+
const params = new URLSearchParams();
|
|
1199
|
+
if (query) params.set("search", query);
|
|
1200
|
+
if (category) params.set("category", category);
|
|
1201
|
+
if (network) params.set("network", network);
|
|
1202
|
+
if (maxPrice !== void 0) params.set("maxPrice", String(maxPrice));
|
|
1203
|
+
if (verifiedOnly) params.set("verified", "true");
|
|
1204
|
+
params.set("sort", sort);
|
|
1205
|
+
params.set("order", "desc");
|
|
1206
|
+
params.set("limit", String(Math.min(limit, 50)));
|
|
1207
|
+
const url = `${marketplaceUrl}?${params.toString()}`;
|
|
1208
|
+
const response = await fetch(url, {
|
|
1209
|
+
headers: { "Accept": "application/json" },
|
|
1210
|
+
signal: AbortSignal.timeout(15e3)
|
|
1211
|
+
});
|
|
1212
|
+
if (!response.ok) {
|
|
1213
|
+
throw new Error(`Marketplace search failed: ${response.status}`);
|
|
1214
|
+
}
|
|
1215
|
+
const data = await response.json();
|
|
1216
|
+
if (!data.resources) return [];
|
|
1217
|
+
return data.resources.map((r) => ({
|
|
1218
|
+
name: r.displayName || r.resourceUrl,
|
|
1219
|
+
url: r.resourceUrl,
|
|
1220
|
+
method: r.method || "GET",
|
|
1221
|
+
price: r.priceLabel || (r.priceUsdc ? `$${r.priceUsdc.toFixed(4)}` : "free"),
|
|
1222
|
+
priceUsdc: r.priceUsdc ?? null,
|
|
1223
|
+
network: r.priceNetwork ?? null,
|
|
1224
|
+
description: r.description || "",
|
|
1225
|
+
category: r.category || "uncategorized",
|
|
1226
|
+
qualityScore: r.qualityScore ?? null,
|
|
1227
|
+
verified: r.verificationStatus === "pass",
|
|
1228
|
+
totalCalls: r.totalSettlements || 0,
|
|
1229
|
+
totalVolume: r.totalVolumeUsdc ? `$${r.totalVolumeUsdc.toLocaleString("en-US", { minimumFractionDigits: 2 })}` : null,
|
|
1230
|
+
seller: r.seller?.displayName ?? null,
|
|
1231
|
+
sellerReputation: r.reputationScore ?? null,
|
|
1232
|
+
authRequired: r.authRequired || false,
|
|
1233
|
+
lastActive: r.lastSettlementAt ?? null
|
|
1234
|
+
}));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/client/budget-account.ts
|
|
1238
|
+
function createBudgetAccount(config) {
|
|
1239
|
+
const { budget, allowedDomains, onPaymentRequired: userOnPayment, ...fetchOptions } = config;
|
|
1240
|
+
const totalBudget = parseFloat(budget.total);
|
|
1241
|
+
const perRequestMax = budget.perRequest ? parseFloat(budget.perRequest) : Infinity;
|
|
1242
|
+
const perHourMax = budget.perHour ? parseFloat(budget.perHour) : Infinity;
|
|
1243
|
+
if (isNaN(totalBudget) || totalBudget <= 0) {
|
|
1244
|
+
throw new Error("budget.total must be a positive number");
|
|
1245
|
+
}
|
|
1246
|
+
let ledger = [];
|
|
1247
|
+
let pendingAmount = 0;
|
|
1248
|
+
function getSpent() {
|
|
1249
|
+
return ledger.reduce((sum, r) => sum + r.amount, 0);
|
|
1250
|
+
}
|
|
1251
|
+
function getHourlySpend() {
|
|
1252
|
+
const cutoff = Date.now() - 36e5;
|
|
1253
|
+
return ledger.filter((r) => r.timestamp >= cutoff).reduce((sum, r) => sum + r.amount, 0);
|
|
1254
|
+
}
|
|
1255
|
+
const innerFetch = wrapFetch(fetch, {
|
|
1256
|
+
...fetchOptions,
|
|
1257
|
+
onPaymentRequired: async (accept) => {
|
|
1258
|
+
const decimals = accept.extra?.decimals ?? 6;
|
|
1259
|
+
const amountUsd = Number(accept.amount) / Math.pow(10, decimals);
|
|
1260
|
+
if (amountUsd > perRequestMax) {
|
|
1261
|
+
throw new X402Error(
|
|
1262
|
+
"amount_exceeds_max",
|
|
1263
|
+
`$${amountUsd.toFixed(4)} exceeds per-request limit of $${perRequestMax.toFixed(2)}`
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
const spent = getSpent();
|
|
1267
|
+
if (spent + amountUsd > totalBudget) {
|
|
1268
|
+
throw new X402Error(
|
|
1269
|
+
"amount_exceeds_max",
|
|
1270
|
+
`Budget exceeded. Spent $${spent.toFixed(2)} of $${totalBudget.toFixed(2)}, payment: $${amountUsd.toFixed(4)}`
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const hourly = getHourlySpend();
|
|
1274
|
+
if (hourly + amountUsd > perHourMax) {
|
|
1275
|
+
throw new X402Error(
|
|
1276
|
+
"amount_exceeds_max",
|
|
1277
|
+
`Hourly limit ($${perHourMax.toFixed(2)}) exceeded. Spent $${hourly.toFixed(2)} this hour`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
pendingAmount = amountUsd;
|
|
1281
|
+
if (userOnPayment) return userOnPayment(accept);
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
const budgetFetch = (async (input, init) => {
|
|
1286
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1287
|
+
let domain = "unknown";
|
|
1288
|
+
try {
|
|
1289
|
+
domain = new URL(url).hostname;
|
|
1290
|
+
} catch {
|
|
1291
|
+
}
|
|
1292
|
+
if (allowedDomains) {
|
|
1293
|
+
if (!allowedDomains.some((d) => domain === d || domain.endsWith(`.${d}`))) {
|
|
1294
|
+
throw new X402Error("payment_rejected", `Domain "${domain}" not in allowed domains`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
pendingAmount = 0;
|
|
1298
|
+
const response = await innerFetch(input, init);
|
|
1299
|
+
if (pendingAmount > 0) {
|
|
1300
|
+
let network = "unknown";
|
|
1301
|
+
const paymentHeader = response.headers.get("PAYMENT-RESPONSE");
|
|
1302
|
+
if (paymentHeader) {
|
|
1303
|
+
try {
|
|
1304
|
+
const decoded = JSON.parse(atob(paymentHeader));
|
|
1305
|
+
network = decoded.network || network;
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
ledger.push({ amount: pendingAmount, domain, network, timestamp: Date.now() });
|
|
1310
|
+
pendingAmount = 0;
|
|
1311
|
+
}
|
|
1312
|
+
return response;
|
|
1313
|
+
});
|
|
1314
|
+
return {
|
|
1315
|
+
fetch: budgetFetch,
|
|
1316
|
+
get spent() {
|
|
1317
|
+
return `$${getSpent().toFixed(2)}`;
|
|
1318
|
+
},
|
|
1319
|
+
get remaining() {
|
|
1320
|
+
return `$${(totalBudget - getSpent()).toFixed(2)}`;
|
|
1321
|
+
},
|
|
1322
|
+
get payments() {
|
|
1323
|
+
return ledger.length;
|
|
1324
|
+
},
|
|
1325
|
+
get spentAmount() {
|
|
1326
|
+
return getSpent();
|
|
1327
|
+
},
|
|
1328
|
+
get remainingAmount() {
|
|
1329
|
+
return totalBudget - getSpent();
|
|
1330
|
+
},
|
|
1331
|
+
get ledger() {
|
|
1332
|
+
return ledger;
|
|
1333
|
+
},
|
|
1334
|
+
get hourlySpend() {
|
|
1335
|
+
return getHourlySpend();
|
|
1336
|
+
},
|
|
1337
|
+
reset() {
|
|
1338
|
+
ledger = [];
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1160
1343
|
// src/client/sponsored-access.ts
|
|
1161
1344
|
function getSponsoredAccessInfo(response) {
|
|
1162
1345
|
const receipt = getPaymentReceipt(response);
|
|
@@ -1185,6 +1368,7 @@ export {
|
|
|
1185
1368
|
SOLANA_MAINNET,
|
|
1186
1369
|
USDC_MINT,
|
|
1187
1370
|
X402Error,
|
|
1371
|
+
createBudgetAccount,
|
|
1188
1372
|
createEvmAdapter,
|
|
1189
1373
|
createEvmKeypairWallet,
|
|
1190
1374
|
createKeypairWallet,
|
|
@@ -1196,6 +1380,6 @@ export {
|
|
|
1196
1380
|
getSponsoredRecommendations,
|
|
1197
1381
|
isEvmKeypairWallet,
|
|
1198
1382
|
isKeypairWallet,
|
|
1383
|
+
searchAPIs,
|
|
1199
1384
|
wrapFetch
|
|
1200
1385
|
};
|
|
1201
|
-
//# sourceMappingURL=index.js.map
|
package/dist/react/index.cjs
CHANGED
|
@@ -499,10 +499,33 @@ function createX402Client(config) {
|
|
|
499
499
|
fetch: customFetch = globalThis.fetch,
|
|
500
500
|
verbose = false,
|
|
501
501
|
accessPass: accessPassConfig,
|
|
502
|
-
onPaymentRequired
|
|
502
|
+
onPaymentRequired,
|
|
503
|
+
maxRetries = 0,
|
|
504
|
+
retryDelayMs = 500
|
|
503
505
|
} = config;
|
|
504
506
|
const log = verbose ? console.log.bind(console, "[x402]") : () => {
|
|
505
507
|
};
|
|
508
|
+
async function fetchWithRetry(input, init) {
|
|
509
|
+
let lastError;
|
|
510
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
511
|
+
try {
|
|
512
|
+
const response = await customFetch(input, init);
|
|
513
|
+
if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
|
|
514
|
+
log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
|
|
515
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
return response;
|
|
519
|
+
} catch (err) {
|
|
520
|
+
lastError = err;
|
|
521
|
+
if (attempt < maxRetries) {
|
|
522
|
+
log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
|
|
523
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
throw lastError;
|
|
528
|
+
}
|
|
506
529
|
const passCache = /* @__PURE__ */ new Map();
|
|
507
530
|
function getCachedPass(url) {
|
|
508
531
|
try {
|
|
@@ -702,7 +725,7 @@ function createX402Client(config) {
|
|
|
702
725
|
}
|
|
703
726
|
}
|
|
704
727
|
}
|
|
705
|
-
const response = await
|
|
728
|
+
const response = await fetchWithRetry(input, init);
|
|
706
729
|
if (response.status !== 402) {
|
|
707
730
|
return response;
|
|
708
731
|
}
|
|
@@ -837,7 +860,7 @@ function createX402Client(config) {
|
|
|
837
860
|
};
|
|
838
861
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
839
862
|
log("Retrying request with payment...");
|
|
840
|
-
const retryResponse = await
|
|
863
|
+
const retryResponse = await fetchWithRetry(input, {
|
|
841
864
|
...init,
|
|
842
865
|
headers: {
|
|
843
866
|
...init?.headers || {},
|
|
@@ -1347,4 +1370,3 @@ function useAccessPass(config) {
|
|
|
1347
1370
|
useAccessPass,
|
|
1348
1371
|
useX402Payment
|
|
1349
1372
|
});
|
|
1350
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/react/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as X402Client } from '../sponsored-access-
|
|
2
|
-
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-
|
|
3
|
-
import { W as WalletSet,
|
|
1
|
+
import { a as X402Client } from '../sponsored-access-BgEDLg_H.cjs';
|
|
2
|
+
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-BgEDLg_H.cjs';
|
|
3
|
+
import { W as WalletSet, d as BalanceInfo } from '../types-DWhpiOBD.cjs';
|
|
4
4
|
import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
5
5
|
export { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
6
6
|
import { a as AccessPassTier } from '../types-CjLMR7qs.cjs';
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as X402Client } from '../sponsored-access-
|
|
2
|
-
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-
|
|
3
|
-
import { W as WalletSet,
|
|
1
|
+
import { a as X402Client } from '../sponsored-access-DjLEKhOV.js';
|
|
2
|
+
export { f as fireImpressionBeacon, b as getSponsoredRecommendations } from '../sponsored-access-DjLEKhOV.js';
|
|
3
|
+
import { W as WalletSet, d as BalanceInfo } from '../types-D1TGACsL.js';
|
|
4
4
|
import { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
5
5
|
export { SponsoredRecommendation } from '@dexterai/x402-ads-types';
|
|
6
6
|
import { a as AccessPassTier } from '../types-CjLMR7qs.js';
|
package/dist/react/index.js
CHANGED
|
@@ -472,10 +472,33 @@ function createX402Client(config) {
|
|
|
472
472
|
fetch: customFetch = globalThis.fetch,
|
|
473
473
|
verbose = false,
|
|
474
474
|
accessPass: accessPassConfig,
|
|
475
|
-
onPaymentRequired
|
|
475
|
+
onPaymentRequired,
|
|
476
|
+
maxRetries = 0,
|
|
477
|
+
retryDelayMs = 500
|
|
476
478
|
} = config;
|
|
477
479
|
const log = verbose ? console.log.bind(console, "[x402]") : () => {
|
|
478
480
|
};
|
|
481
|
+
async function fetchWithRetry(input, init) {
|
|
482
|
+
let lastError;
|
|
483
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
484
|
+
try {
|
|
485
|
+
const response = await customFetch(input, init);
|
|
486
|
+
if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
|
|
487
|
+
log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
|
|
488
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
return response;
|
|
492
|
+
} catch (err) {
|
|
493
|
+
lastError = err;
|
|
494
|
+
if (attempt < maxRetries) {
|
|
495
|
+
log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
|
|
496
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
throw lastError;
|
|
501
|
+
}
|
|
479
502
|
const passCache = /* @__PURE__ */ new Map();
|
|
480
503
|
function getCachedPass(url) {
|
|
481
504
|
try {
|
|
@@ -675,7 +698,7 @@ function createX402Client(config) {
|
|
|
675
698
|
}
|
|
676
699
|
}
|
|
677
700
|
}
|
|
678
|
-
const response = await
|
|
701
|
+
const response = await fetchWithRetry(input, init);
|
|
679
702
|
if (response.status !== 402) {
|
|
680
703
|
return response;
|
|
681
704
|
}
|
|
@@ -810,7 +833,7 @@ function createX402Client(config) {
|
|
|
810
833
|
};
|
|
811
834
|
const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
|
|
812
835
|
log("Retrying request with payment...");
|
|
813
|
-
const retryResponse = await
|
|
836
|
+
const retryResponse = await fetchWithRetry(input, {
|
|
814
837
|
...init,
|
|
815
838
|
headers: {
|
|
816
839
|
...init?.headers || {},
|
|
@@ -1319,4 +1342,3 @@ export {
|
|
|
1319
1342
|
useAccessPass,
|
|
1320
1343
|
useX402Payment
|
|
1321
1344
|
};
|
|
1322
|
-
//# sourceMappingURL=index.js.map
|
package/dist/server/index.cjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ChainAdapter, W as WalletSet } from './types-
|
|
1
|
+
import { C as ChainAdapter, W as WalletSet } from './types-DWhpiOBD.cjs';
|
|
2
2
|
import { A as AccessPassClientConfig, P as PaymentAccept } from './types-CjLMR7qs.cjs';
|
|
3
3
|
import { SponsoredRecommendation, SponsoredAccessSettlementInfo } from '@dexterai/x402-ads-types';
|
|
4
4
|
|
|
@@ -112,6 +112,17 @@ interface X402ClientConfig {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
onPaymentRequired?: (requirements: PaymentAccept) => boolean | Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* Maximum retry attempts for transient failures (network errors, 502/503).
|
|
117
|
+
* Does not cause double payments — EIP-3009 nonces prevent replay.
|
|
118
|
+
* @default 0 (no retry)
|
|
119
|
+
*/
|
|
120
|
+
maxRetries?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Base delay between retries in milliseconds (doubles each attempt).
|
|
123
|
+
* @default 500
|
|
124
|
+
*/
|
|
125
|
+
retryDelayMs?: number;
|
|
115
126
|
}
|
|
116
127
|
/**
|
|
117
128
|
* x402 Client interface
|