@dexterai/x402 1.9.3 → 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.
Files changed (39) hide show
  1. package/dist/adapters/index.cjs +14 -6
  2. package/dist/adapters/index.d.cts +3 -5
  3. package/dist/adapters/index.d.ts +3 -5
  4. package/dist/adapters/index.js +14 -6
  5. package/dist/client/index.cjs +251 -29
  6. package/dist/client/index.d.cts +215 -37
  7. package/dist/client/index.d.ts +215 -37
  8. package/dist/client/index.js +248 -29
  9. package/dist/react/index.cjs +88 -33
  10. package/dist/react/index.d.cts +10 -5
  11. package/dist/react/index.d.ts +10 -5
  12. package/dist/react/index.js +88 -33
  13. package/dist/server/index.cjs +27 -4
  14. package/dist/server/index.d.cts +45 -13
  15. package/dist/server/index.d.ts +45 -13
  16. package/dist/server/index.js +27 -4
  17. package/dist/{sponsored-access-H1EX6zpi.d.ts → sponsored-access-BgEDLg_H.d.cts} +31 -2
  18. package/dist/{sponsored-access-BCB2CxdG.d.cts → sponsored-access-DjLEKhOV.d.ts} +31 -2
  19. package/dist/{types-BQvaF8lB.d.cts → types-CjLMR7qs.d.cts} +1 -1
  20. package/dist/{types-BQvaF8lB.d.ts → types-CjLMR7qs.d.ts} +1 -1
  21. package/dist/types-D1TGACsL.d.ts +245 -0
  22. package/dist/types-DWhpiOBD.d.cts +245 -0
  23. package/dist/utils/index.cjs +8 -7
  24. package/dist/utils/index.js +8 -7
  25. package/package.json +1 -1
  26. package/dist/adapters/index.cjs.map +0 -1
  27. package/dist/adapters/index.js.map +0 -1
  28. package/dist/client/index.cjs.map +0 -1
  29. package/dist/client/index.js.map +0 -1
  30. package/dist/react/index.cjs.map +0 -1
  31. package/dist/react/index.js.map +0 -1
  32. package/dist/server/index.cjs.map +0 -1
  33. package/dist/server/index.js.map +0 -1
  34. package/dist/solana-CfHuiW2H.d.cts +0 -132
  35. package/dist/solana-kZcwbUK9.d.ts +0 -132
  36. package/dist/types-DmqH9yD8.d.cts +0 -123
  37. package/dist/types-ENcnkof8.d.ts +0 -123
  38. package/dist/utils/index.cjs.map +0 -1
  39. package/dist/utils/index.js.map +0 -1
@@ -131,8 +131,11 @@ var SolanaAdapter = class {
131
131
  const account = await (0, import_spl_token.getAccount)(connection, ata, void 0, programId);
132
132
  const decimals = accept.extra?.decimals ?? 6;
133
133
  return Number(account.amount) / Math.pow(10, decimals);
134
- } catch {
135
- return 0;
134
+ } catch (err) {
135
+ if (err && typeof err === "object" && "name" in err && (err.name === "TokenAccountNotFoundError" || err.name === "TokenInvalidAccountOwnerError")) {
136
+ return 0;
137
+ }
138
+ throw err;
136
139
  }
137
140
  }
138
141
  async buildTransaction(accept, wallet, rpcUrl) {
@@ -360,15 +363,21 @@ var EvmAdapter = class {
360
363
  ]
361
364
  })
362
365
  });
366
+ if (!response.ok) {
367
+ throw new Error(`RPC request failed: ${response.status}`);
368
+ }
363
369
  const result = await response.json();
364
- if (result.error || !result.result) {
370
+ if (result.error) {
371
+ throw new Error(`RPC error: ${JSON.stringify(result.error)}`);
372
+ }
373
+ if (!result.result || result.result === "0x") {
365
374
  return 0;
366
375
  }
367
376
  const balance = BigInt(result.result);
368
377
  const decimals = accept.extra?.decimals ?? 6;
369
378
  return Number(balance) / Math.pow(10, decimals);
370
- } catch {
371
- return 0;
379
+ } catch (err) {
380
+ throw err;
372
381
  }
373
382
  }
374
383
  encodeBalanceOf(address) {
@@ -502,4 +511,3 @@ function findAdapter(adapters, network) {
502
511
  isKnownUSDC,
503
512
  isSolanaWallet
504
513
  });
505
- //# sourceMappingURL=index.cjs.map
@@ -1,8 +1,6 @@
1
- import { C as ChainAdapter } from '../types-DmqH9yD8.cjs';
2
- export { A as AdapterConfig, B as BalanceInfo, G as GenericWallet, S as SignedTransaction, W as WalletSet } from '../types-DmqH9yD8.cjs';
3
- import { b as SolanaAdapter, d as EvmAdapter } from '../solana-CfHuiW2H.cjs';
4
- export { A as ARBITRUM_ONE, k as AVALANCHE, B as BASE_MAINNET, j as BASE_SEPOLIA, n as ETHEREUM_MAINNET, E as EvmWallet, O as OPTIMISM, P as POLYGON, l as SKALE_BASE, m as SKALE_BASE_SEPOLIA, e as SOLANA_DEVNET, S as SOLANA_MAINNET, f as SOLANA_TESTNET, g as SolanaWallet, U as USDC_ADDRESSES, a as createEvmAdapter, c as createSolanaAdapter, h as isEvmWallet, i as isSolanaWallet } from '../solana-CfHuiW2H.cjs';
5
- import '../types-BQvaF8lB.cjs';
1
+ import { e as SolanaAdapter, f as EvmAdapter, C as ChainAdapter } from '../types-DWhpiOBD.cjs';
2
+ export { m as ARBITRUM_ONE, n as AVALANCHE, A as AdapterConfig, B as BASE_MAINNET, l as BASE_SEPOLIA, d as BalanceInfo, q as ETHEREUM_MAINNET, E as EvmWallet, G as GenericWallet, O as OPTIMISM, P as POLYGON, o as SKALE_BASE, p as SKALE_BASE_SEPOLIA, h as SOLANA_DEVNET, b as SOLANA_MAINNET, j as SOLANA_TESTNET, g as SignedTransaction, S as SolanaWallet, U as USDC_ADDRESSES, W as WalletSet, a as createEvmAdapter, c as createSolanaAdapter, k as isEvmWallet, i as isSolanaWallet } from '../types-DWhpiOBD.cjs';
3
+ import '../types-CjLMR7qs.cjs';
6
4
 
7
5
  /**
8
6
  * Check if an asset address is a known USDC contract (any chain).
@@ -1,8 +1,6 @@
1
- import { C as ChainAdapter } from '../types-ENcnkof8.js';
2
- export { A as AdapterConfig, B as BalanceInfo, G as GenericWallet, S as SignedTransaction, W as WalletSet } from '../types-ENcnkof8.js';
3
- import { b as SolanaAdapter, d as EvmAdapter } from '../solana-kZcwbUK9.js';
4
- export { A as ARBITRUM_ONE, k as AVALANCHE, B as BASE_MAINNET, j as BASE_SEPOLIA, n as ETHEREUM_MAINNET, E as EvmWallet, O as OPTIMISM, P as POLYGON, l as SKALE_BASE, m as SKALE_BASE_SEPOLIA, e as SOLANA_DEVNET, S as SOLANA_MAINNET, f as SOLANA_TESTNET, g as SolanaWallet, U as USDC_ADDRESSES, a as createEvmAdapter, c as createSolanaAdapter, h as isEvmWallet, i as isSolanaWallet } from '../solana-kZcwbUK9.js';
5
- import '../types-BQvaF8lB.js';
1
+ import { e as SolanaAdapter, f as EvmAdapter, C as ChainAdapter } from '../types-D1TGACsL.js';
2
+ export { m as ARBITRUM_ONE, n as AVALANCHE, A as AdapterConfig, B as BASE_MAINNET, l as BASE_SEPOLIA, d as BalanceInfo, q as ETHEREUM_MAINNET, E as EvmWallet, G as GenericWallet, O as OPTIMISM, P as POLYGON, o as SKALE_BASE, p as SKALE_BASE_SEPOLIA, h as SOLANA_DEVNET, b as SOLANA_MAINNET, j as SOLANA_TESTNET, g as SignedTransaction, S as SolanaWallet, U as USDC_ADDRESSES, W as WalletSet, a as createEvmAdapter, c as createSolanaAdapter, k as isEvmWallet, i as isSolanaWallet } from '../types-D1TGACsL.js';
3
+ import '../types-CjLMR7qs.js';
6
4
 
7
5
  /**
8
6
  * Check if an asset address is a known USDC contract (any chain).
@@ -87,8 +87,11 @@ var SolanaAdapter = class {
87
87
  const account = await getAccount(connection, ata, void 0, programId);
88
88
  const decimals = accept.extra?.decimals ?? 6;
89
89
  return Number(account.amount) / Math.pow(10, decimals);
90
- } catch {
91
- return 0;
90
+ } catch (err) {
91
+ if (err && typeof err === "object" && "name" in err && (err.name === "TokenAccountNotFoundError" || err.name === "TokenInvalidAccountOwnerError")) {
92
+ return 0;
93
+ }
94
+ throw err;
92
95
  }
93
96
  }
94
97
  async buildTransaction(accept, wallet, rpcUrl) {
@@ -316,15 +319,21 @@ var EvmAdapter = class {
316
319
  ]
317
320
  })
318
321
  });
322
+ if (!response.ok) {
323
+ throw new Error(`RPC request failed: ${response.status}`);
324
+ }
319
325
  const result = await response.json();
320
- if (result.error || !result.result) {
326
+ if (result.error) {
327
+ throw new Error(`RPC error: ${JSON.stringify(result.error)}`);
328
+ }
329
+ if (!result.result || result.result === "0x") {
321
330
  return 0;
322
331
  }
323
332
  const balance = BigInt(result.result);
324
333
  const decimals = accept.extra?.decimals ?? 6;
325
334
  return Number(balance) / Math.pow(10, decimals);
326
- } catch {
327
- return 0;
335
+ } catch (err) {
336
+ throw err;
328
337
  }
329
338
  }
330
339
  encodeBalanceOf(address) {
@@ -457,4 +466,3 @@ export {
457
466
  isKnownUSDC,
458
467
  isSolanaWallet
459
468
  };
460
- //# sourceMappingURL=index.js.map
@@ -187,9 +187,11 @@ var client_exports = {};
187
187
  __export(client_exports, {
188
188
  BASE_MAINNET: () => BASE_MAINNET,
189
189
  DEXTER_FACILITATOR_URL: () => DEXTER_FACILITATOR_URL,
190
+ KEYPAIR_SYMBOL: () => KEYPAIR_SYMBOL,
190
191
  SOLANA_MAINNET: () => SOLANA_MAINNET,
191
192
  USDC_MINT: () => USDC_MINT,
192
193
  X402Error: () => X402Error,
194
+ createBudgetAccount: () => createBudgetAccount,
193
195
  createEvmAdapter: () => createEvmAdapter,
194
196
  createEvmKeypairWallet: () => createEvmKeypairWallet,
195
197
  createKeypairWallet: () => createKeypairWallet,
@@ -201,6 +203,7 @@ __export(client_exports, {
201
203
  getSponsoredRecommendations: () => getSponsoredRecommendations,
202
204
  isEvmKeypairWallet: () => isEvmKeypairWallet,
203
205
  isKeypairWallet: () => isKeypairWallet,
206
+ searchAPIs: () => searchAPIs,
204
207
  wrapFetch: () => wrapFetch
205
208
  });
206
209
  module.exports = __toCommonJS(client_exports);
@@ -298,8 +301,11 @@ var SolanaAdapter = class {
298
301
  const account = await (0, import_spl_token.getAccount)(connection, ata, void 0, programId);
299
302
  const decimals = accept.extra?.decimals ?? 6;
300
303
  return Number(account.amount) / Math.pow(10, decimals);
301
- } catch {
302
- return 0;
304
+ } catch (err) {
305
+ if (err && typeof err === "object" && "name" in err && (err.name === "TokenAccountNotFoundError" || err.name === "TokenInvalidAccountOwnerError")) {
306
+ return 0;
307
+ }
308
+ throw err;
303
309
  }
304
310
  }
305
311
  async buildTransaction(accept, wallet, rpcUrl) {
@@ -527,15 +533,21 @@ var EvmAdapter = class {
527
533
  ]
528
534
  })
529
535
  });
536
+ if (!response.ok) {
537
+ throw new Error(`RPC request failed: ${response.status}`);
538
+ }
530
539
  const result = await response.json();
531
- if (result.error || !result.result) {
540
+ if (result.error) {
541
+ throw new Error(`RPC error: ${JSON.stringify(result.error)}`);
542
+ }
543
+ if (!result.result || result.result === "0x") {
532
544
  return 0;
533
545
  }
534
546
  const balance = BigInt(result.result);
535
547
  const decimals = accept.extra?.decimals ?? 6;
536
548
  return Number(balance) / Math.pow(10, decimals);
537
- } catch {
538
- return 0;
549
+ } catch (err) {
550
+ throw err;
539
551
  }
540
552
  }
541
553
  encodeBalanceOf(address) {
@@ -651,10 +663,34 @@ function createX402Client(config) {
651
663
  maxAmountAtomic,
652
664
  fetch: customFetch = globalThis.fetch,
653
665
  verbose = false,
654
- accessPass: accessPassConfig
666
+ accessPass: accessPassConfig,
667
+ onPaymentRequired,
668
+ maxRetries = 0,
669
+ retryDelayMs = 500
655
670
  } = config;
656
671
  const log = verbose ? console.log.bind(console, "[x402]") : () => {
657
672
  };
673
+ async function fetchWithRetry(input, init) {
674
+ let lastError;
675
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
676
+ try {
677
+ const response = await customFetch(input, init);
678
+ if (response.status >= 502 && response.status <= 504 && attempt < maxRetries) {
679
+ log(`Retry ${attempt + 1}/${maxRetries}: server returned ${response.status}`);
680
+ await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
681
+ continue;
682
+ }
683
+ return response;
684
+ } catch (err) {
685
+ lastError = err;
686
+ if (attempt < maxRetries) {
687
+ log(`Retry ${attempt + 1}/${maxRetries}: ${err instanceof Error ? err.message : "network error"}`);
688
+ await new Promise((r) => setTimeout(r, retryDelayMs * Math.pow(2, attempt)));
689
+ }
690
+ }
691
+ }
692
+ throw lastError;
693
+ }
658
694
  const passCache = /* @__PURE__ */ new Map();
659
695
  function getCachedPass(url) {
660
696
  try {
@@ -764,13 +800,17 @@ function createX402Client(config) {
764
800
  const paymentAmount = accept.amount ?? accept.maxAmountRequired;
765
801
  if (!paymentAmount) return null;
766
802
  const rpcUrl = getRpcUrl(accept.network, adapter);
767
- const balance = await adapter.getBalance(accept, wallet, rpcUrl);
768
- const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
769
- if (balance < requiredAmount) {
770
- throw new X402Error(
771
- "insufficient_balance",
772
- `Insufficient balance for access pass. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
773
- );
803
+ try {
804
+ const balance = await adapter.getBalance(accept, wallet, rpcUrl);
805
+ const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
806
+ if (balance < requiredAmount) {
807
+ throw new X402Error(
808
+ "insufficient_balance",
809
+ `Insufficient balance for access pass. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
810
+ );
811
+ }
812
+ } catch (err) {
813
+ if (err instanceof X402Error) throw err;
774
814
  }
775
815
  const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
776
816
  let payload;
@@ -783,13 +823,19 @@ function createX402Client(config) {
783
823
  let resolvedResource = requirements.resource;
784
824
  if (typeof requirements.resource === "string") {
785
825
  try {
786
- resolvedResource = new URL(requirements.resource, originalUrl).toString();
826
+ const resolved = new URL(requirements.resource, originalUrl);
827
+ if (["http:", "https:"].includes(resolved.protocol)) {
828
+ resolvedResource = resolved.toString();
829
+ }
787
830
  } catch {
788
831
  }
789
832
  } else if (requirements.resource && typeof requirements.resource === "object" && "url" in requirements.resource) {
790
833
  const rObj = requirements.resource;
791
834
  try {
792
- resolvedResource = { ...rObj, url: new URL(rObj.url, originalUrl).toString() };
835
+ const resolved = new URL(rObj.url, originalUrl);
836
+ if (["http:", "https:"].includes(resolved.protocol)) {
837
+ resolvedResource = { ...rObj, url: resolved.toString() };
838
+ }
793
839
  } catch {
794
840
  }
795
841
  }
@@ -844,7 +890,7 @@ function createX402Client(config) {
844
890
  }
845
891
  }
846
892
  }
847
- const response = await customFetch(input, init);
893
+ const response = await fetchWithRetry(input, init);
848
894
  if (response.status !== 402) {
849
895
  return response;
850
896
  }
@@ -917,16 +963,27 @@ function createX402Client(config) {
917
963
  }
918
964
  const rpcUrl = getRpcUrl(accept.network, adapter);
919
965
  log("Checking balance...");
920
- const balance = await adapter.getBalance(accept, wallet, rpcUrl);
921
- const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
922
- if (balance < requiredAmount) {
923
- const network = adapter.name === "EVM" ? "Base" : "Solana";
924
- throw new X402Error(
925
- "insufficient_balance",
926
- `Insufficient USDC balance on ${network}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
927
- );
966
+ try {
967
+ const balance = await adapter.getBalance(accept, wallet, rpcUrl);
968
+ const requiredAmount = Number(paymentAmount) / Math.pow(10, decimals);
969
+ if (balance < requiredAmount) {
970
+ const network = adapter.name === "EVM" ? "Base" : "Solana";
971
+ throw new X402Error(
972
+ "insufficient_balance",
973
+ `Insufficient USDC balance on ${network}. Have $${balance.toFixed(4)}, need $${requiredAmount.toFixed(4)}`
974
+ );
975
+ }
976
+ log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
977
+ } catch (err) {
978
+ if (err instanceof X402Error) throw err;
979
+ log("Balance check failed (RPC error), proceeding with transaction attempt");
980
+ }
981
+ if (onPaymentRequired) {
982
+ const approved = await onPaymentRequired(accept);
983
+ if (!approved) {
984
+ throw new X402Error("payment_rejected", "Payment rejected by onPaymentRequired callback");
985
+ }
928
986
  }
929
- log(`Balance OK: $${balance.toFixed(4)} >= $${requiredAmount.toFixed(4)}`);
930
987
  log("Building transaction...");
931
988
  const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
932
989
  log("Transaction signed");
@@ -968,7 +1025,7 @@ function createX402Client(config) {
968
1025
  };
969
1026
  const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
970
1027
  log("Retrying request with payment...");
971
- const retryResponse = await customFetch(input, {
1028
+ const retryResponse = await fetchWithRetry(input, {
972
1029
  ...init,
973
1030
  headers: {
974
1031
  ...init?.headers || {},
@@ -1012,6 +1069,7 @@ function createX402Client(config) {
1012
1069
 
1013
1070
  // src/client/keypair-wallet.ts
1014
1071
  var import_web32 = require("@solana/web3.js");
1072
+ var KEYPAIR_SYMBOL = /* @__PURE__ */ Symbol.for("x402:keypair");
1015
1073
  async function createKeypairWallet(privateKey) {
1016
1074
  let keypair;
1017
1075
  if (typeof privateKey === "string") {
@@ -1066,7 +1124,9 @@ async function createKeypairWallet(privateKey) {
1066
1124
  }
1067
1125
  throw new Error("Unknown transaction type");
1068
1126
  },
1127
+ [KEYPAIR_SYMBOL]: keypair,
1069
1128
  keypair
1129
+ // deprecated — kept for backwards compat
1070
1130
  };
1071
1131
  }
1072
1132
  function isKeypairWallet(wallet) {
@@ -1109,7 +1169,8 @@ function wrapFetch(fetchImpl, options) {
1109
1169
  rpcUrls,
1110
1170
  maxAmountAtomic,
1111
1171
  verbose,
1112
- accessPass
1172
+ accessPass,
1173
+ onPaymentRequired
1113
1174
  } = options;
1114
1175
  if (!walletPrivateKey && !evmPrivateKey) {
1115
1176
  throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");
@@ -1142,7 +1203,8 @@ function wrapFetch(fetchImpl, options) {
1142
1203
  maxAmountAtomic,
1143
1204
  fetch: fetchImpl,
1144
1205
  verbose,
1145
- accessPass
1206
+ accessPass,
1207
+ onPaymentRequired
1146
1208
  };
1147
1209
  const client = createX402Client(clientConfig);
1148
1210
  const clientFetch = client.fetch.bind(client);
@@ -1155,6 +1217,164 @@ function wrapFetch(fetchImpl, options) {
1155
1217
  return clientFetch;
1156
1218
  }
1157
1219
 
1220
+ // src/client/discovery.ts
1221
+ var DEFAULT_MARKETPLACE = "https://x402.dexter.cash/api/facilitator/marketplace/resources";
1222
+ async function searchAPIs(options = {}) {
1223
+ const {
1224
+ query,
1225
+ category,
1226
+ network,
1227
+ maxPrice,
1228
+ verifiedOnly,
1229
+ sort = "marketplace",
1230
+ limit = 20,
1231
+ marketplaceUrl = DEFAULT_MARKETPLACE
1232
+ } = options;
1233
+ const params = new URLSearchParams();
1234
+ if (query) params.set("search", query);
1235
+ if (category) params.set("category", category);
1236
+ if (network) params.set("network", network);
1237
+ if (maxPrice !== void 0) params.set("maxPrice", String(maxPrice));
1238
+ if (verifiedOnly) params.set("verified", "true");
1239
+ params.set("sort", sort);
1240
+ params.set("order", "desc");
1241
+ params.set("limit", String(Math.min(limit, 50)));
1242
+ const url = `${marketplaceUrl}?${params.toString()}`;
1243
+ const response = await fetch(url, {
1244
+ headers: { "Accept": "application/json" },
1245
+ signal: AbortSignal.timeout(15e3)
1246
+ });
1247
+ if (!response.ok) {
1248
+ throw new Error(`Marketplace search failed: ${response.status}`);
1249
+ }
1250
+ const data = await response.json();
1251
+ if (!data.resources) return [];
1252
+ return data.resources.map((r) => ({
1253
+ name: r.displayName || r.resourceUrl,
1254
+ url: r.resourceUrl,
1255
+ method: r.method || "GET",
1256
+ price: r.priceLabel || (r.priceUsdc ? `$${r.priceUsdc.toFixed(4)}` : "free"),
1257
+ priceUsdc: r.priceUsdc ?? null,
1258
+ network: r.priceNetwork ?? null,
1259
+ description: r.description || "",
1260
+ category: r.category || "uncategorized",
1261
+ qualityScore: r.qualityScore ?? null,
1262
+ verified: r.verificationStatus === "pass",
1263
+ totalCalls: r.totalSettlements || 0,
1264
+ totalVolume: r.totalVolumeUsdc ? `$${r.totalVolumeUsdc.toLocaleString("en-US", { minimumFractionDigits: 2 })}` : null,
1265
+ seller: r.seller?.displayName ?? null,
1266
+ sellerReputation: r.reputationScore ?? null,
1267
+ authRequired: r.authRequired || false,
1268
+ lastActive: r.lastSettlementAt ?? null
1269
+ }));
1270
+ }
1271
+
1272
+ // src/client/budget-account.ts
1273
+ function createBudgetAccount(config) {
1274
+ const { budget, allowedDomains, onPaymentRequired: userOnPayment, ...fetchOptions } = config;
1275
+ const totalBudget = parseFloat(budget.total);
1276
+ const perRequestMax = budget.perRequest ? parseFloat(budget.perRequest) : Infinity;
1277
+ const perHourMax = budget.perHour ? parseFloat(budget.perHour) : Infinity;
1278
+ if (isNaN(totalBudget) || totalBudget <= 0) {
1279
+ throw new Error("budget.total must be a positive number");
1280
+ }
1281
+ let ledger = [];
1282
+ let pendingAmount = 0;
1283
+ function getSpent() {
1284
+ return ledger.reduce((sum, r) => sum + r.amount, 0);
1285
+ }
1286
+ function getHourlySpend() {
1287
+ const cutoff = Date.now() - 36e5;
1288
+ return ledger.filter((r) => r.timestamp >= cutoff).reduce((sum, r) => sum + r.amount, 0);
1289
+ }
1290
+ const innerFetch = wrapFetch(fetch, {
1291
+ ...fetchOptions,
1292
+ onPaymentRequired: async (accept) => {
1293
+ const decimals = accept.extra?.decimals ?? 6;
1294
+ const amountUsd = Number(accept.amount) / Math.pow(10, decimals);
1295
+ if (amountUsd > perRequestMax) {
1296
+ throw new X402Error(
1297
+ "amount_exceeds_max",
1298
+ `$${amountUsd.toFixed(4)} exceeds per-request limit of $${perRequestMax.toFixed(2)}`
1299
+ );
1300
+ }
1301
+ const spent = getSpent();
1302
+ if (spent + amountUsd > totalBudget) {
1303
+ throw new X402Error(
1304
+ "amount_exceeds_max",
1305
+ `Budget exceeded. Spent $${spent.toFixed(2)} of $${totalBudget.toFixed(2)}, payment: $${amountUsd.toFixed(4)}`
1306
+ );
1307
+ }
1308
+ const hourly = getHourlySpend();
1309
+ if (hourly + amountUsd > perHourMax) {
1310
+ throw new X402Error(
1311
+ "amount_exceeds_max",
1312
+ `Hourly limit ($${perHourMax.toFixed(2)}) exceeded. Spent $${hourly.toFixed(2)} this hour`
1313
+ );
1314
+ }
1315
+ pendingAmount = amountUsd;
1316
+ if (userOnPayment) return userOnPayment(accept);
1317
+ return true;
1318
+ }
1319
+ });
1320
+ const budgetFetch = (async (input, init) => {
1321
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1322
+ let domain = "unknown";
1323
+ try {
1324
+ domain = new URL(url).hostname;
1325
+ } catch {
1326
+ }
1327
+ if (allowedDomains) {
1328
+ if (!allowedDomains.some((d) => domain === d || domain.endsWith(`.${d}`))) {
1329
+ throw new X402Error("payment_rejected", `Domain "${domain}" not in allowed domains`);
1330
+ }
1331
+ }
1332
+ pendingAmount = 0;
1333
+ const response = await innerFetch(input, init);
1334
+ if (pendingAmount > 0) {
1335
+ let network = "unknown";
1336
+ const paymentHeader = response.headers.get("PAYMENT-RESPONSE");
1337
+ if (paymentHeader) {
1338
+ try {
1339
+ const decoded = JSON.parse(atob(paymentHeader));
1340
+ network = decoded.network || network;
1341
+ } catch {
1342
+ }
1343
+ }
1344
+ ledger.push({ amount: pendingAmount, domain, network, timestamp: Date.now() });
1345
+ pendingAmount = 0;
1346
+ }
1347
+ return response;
1348
+ });
1349
+ return {
1350
+ fetch: budgetFetch,
1351
+ get spent() {
1352
+ return `$${getSpent().toFixed(2)}`;
1353
+ },
1354
+ get remaining() {
1355
+ return `$${(totalBudget - getSpent()).toFixed(2)}`;
1356
+ },
1357
+ get payments() {
1358
+ return ledger.length;
1359
+ },
1360
+ get spentAmount() {
1361
+ return getSpent();
1362
+ },
1363
+ get remainingAmount() {
1364
+ return totalBudget - getSpent();
1365
+ },
1366
+ get ledger() {
1367
+ return ledger;
1368
+ },
1369
+ get hourlySpend() {
1370
+ return getHourlySpend();
1371
+ },
1372
+ reset() {
1373
+ ledger = [];
1374
+ }
1375
+ };
1376
+ }
1377
+
1158
1378
  // src/client/sponsored-access.ts
1159
1379
  function getSponsoredAccessInfo(response) {
1160
1380
  const receipt = getPaymentReceipt(response);
@@ -1180,9 +1400,11 @@ async function fireImpressionBeacon(response) {
1180
1400
  0 && (module.exports = {
1181
1401
  BASE_MAINNET,
1182
1402
  DEXTER_FACILITATOR_URL,
1403
+ KEYPAIR_SYMBOL,
1183
1404
  SOLANA_MAINNET,
1184
1405
  USDC_MINT,
1185
1406
  X402Error,
1407
+ createBudgetAccount,
1186
1408
  createEvmAdapter,
1187
1409
  createEvmKeypairWallet,
1188
1410
  createKeypairWallet,
@@ -1194,6 +1416,6 @@ async function fireImpressionBeacon(response) {
1194
1416
  getSponsoredRecommendations,
1195
1417
  isEvmKeypairWallet,
1196
1418
  isKeypairWallet,
1419
+ searchAPIs,
1197
1420
  wrapFetch
1198
1421
  });
1199
- //# sourceMappingURL=index.cjs.map