@routstr/sdk 0.2.5 → 0.2.7

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/index.mjs CHANGED
@@ -2,16 +2,8 @@ import { RelayPool, onlyEvents } from 'applesauce-relay';
2
2
  import { EventStore } from 'applesauce-core';
3
3
  import { tap } from 'rxjs';
4
4
  import { createStore } from 'zustand/vanilla';
5
- import { getDecodedToken } from '@cashu/cashu-ts';
6
5
  import { Transform, Readable } from 'stream';
7
6
 
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
-
15
7
  // core/errors.ts
16
8
  var InsufficientBalanceError = class extends Error {
17
9
  constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
@@ -474,7 +466,7 @@ var ModelManager = class _ModelManager {
474
466
  }
475
467
  }
476
468
  const DEFAULT_RELAYS = [
477
- "wss://relay.primal.net",
469
+ "wss://relay.damus.io",
478
470
  "wss://nos.lol",
479
471
  "wss://relay.routstr.com"
480
472
  ];
@@ -786,13 +778,8 @@ var CashuSpender = class {
786
778
  normalizedMintBalances[url] = balanceInSats;
787
779
  totalMintBalance += balanceInSats;
788
780
  }
789
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
790
781
  const providerBalances = {};
791
782
  let totalProviderBalance = 0;
792
- for (const pending of pendingDistribution) {
793
- providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
794
- totalProviderBalance += pending.amount;
795
- }
796
783
  const apiKeys = this.storageAdapter.getAllApiKeys();
797
784
  for (const apiKey of apiKeys) {
798
785
  if (!providerBalances[apiKey.baseUrl]) {
@@ -1013,27 +1000,11 @@ var CashuSpender = class {
1013
1000
  };
1014
1001
  }
1015
1002
  }
1016
- if (token && baseUrl) {
1017
- try {
1018
- this.storageAdapter.setToken(baseUrl, token);
1019
- } catch (error) {
1020
- if (error instanceof Error && error.message.includes("Token already exists")) {
1021
- this._log(
1022
- "DEBUG",
1023
- `[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
1024
- );
1025
- const receiveResult = await this.receiveToken(token);
1026
- if (receiveResult.success) {
1027
- this._log(
1028
- "DEBUG",
1029
- `[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
1030
- );
1031
- }
1032
- token = this.storageAdapter.getToken(baseUrl);
1033
- } else {
1034
- throw error;
1035
- }
1036
- }
1003
+ if (token) {
1004
+ this._log(
1005
+ "DEBUG",
1006
+ `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
1007
+ );
1037
1008
  }
1038
1009
  this._logTransaction("spend", {
1039
1010
  amount: spentAmount,
@@ -1054,19 +1025,19 @@ var CashuSpender = class {
1054
1025
  };
1055
1026
  }
1056
1027
  /**
1057
- * Try to reuse an existing token
1028
+ * Try to reuse an existing API key
1058
1029
  */
1059
1030
  async _tryReuseToken(baseUrl, amount, mintUrl) {
1060
- const storedToken = this.storageAdapter.getToken(baseUrl);
1061
- if (!storedToken) return null;
1062
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1063
- const balanceForBaseUrl = pendingDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
1064
- this._log("DEBUG", "RESUINGDSR GSODGNSD", balanceForBaseUrl, amount);
1031
+ const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
1032
+ if (!apiKeyEntry) return null;
1033
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1034
+ const balanceForBaseUrl = apiKeyDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
1035
+ this._log("DEBUG", "Reusing API key", balanceForBaseUrl, amount);
1065
1036
  if (balanceForBaseUrl > amount) {
1066
1037
  const units = this.walletAdapter.getMintUnits();
1067
1038
  const unit = units[mintUrl] || "sat";
1068
1039
  return {
1069
- token: storedToken,
1040
+ token: apiKeyEntry.key,
1070
1041
  status: "success",
1071
1042
  balance: balanceForBaseUrl,
1072
1043
  unit
@@ -1077,7 +1048,8 @@ var CashuSpender = class {
1077
1048
  const topUpResult = await this.balanceManager.topUp({
1078
1049
  mintUrl,
1079
1050
  baseUrl,
1080
- amount: topUpAmount
1051
+ amount: topUpAmount,
1052
+ token: apiKeyEntry.key
1081
1053
  });
1082
1054
  this._log("DEBUG", "TOPUP ", topUpResult);
1083
1055
  if (topUpResult.success && topUpResult.toppedUpAmount) {
@@ -1091,7 +1063,7 @@ var CashuSpender = class {
1091
1063
  status: "success"
1092
1064
  });
1093
1065
  return {
1094
- token: storedToken,
1066
+ token: apiKeyEntry.key,
1095
1067
  status: "success",
1096
1068
  balance: newBalance,
1097
1069
  unit
@@ -1099,84 +1071,131 @@ var CashuSpender = class {
1099
1071
  }
1100
1072
  const providerBalance = await this._getProviderTokenBalance(
1101
1073
  baseUrl,
1102
- storedToken
1074
+ apiKeyEntry.key
1103
1075
  );
1104
1076
  this._log("DEBUG", providerBalance);
1105
1077
  if (providerBalance <= 0) {
1106
- this.storageAdapter.removeToken(baseUrl);
1078
+ this.storageAdapter.removeApiKey(baseUrl);
1107
1079
  }
1108
1080
  }
1109
1081
  return null;
1110
1082
  }
1111
1083
  /**
1112
- * Refund specific providers without retrying spend
1084
+ * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
1085
+ * The xcashu token acts as an API key to claim the refund, and the response contains
1086
+ * the actual refunded Cashu token which is then received into the wallet.
1087
+ * @param mintUrl - The mint URL for receiving tokens
1088
+ * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
1089
+ * @returns Results for each xcashu token refund attempt
1113
1090
  */
1114
- async refundProviders(baseUrls, mintUrl, refundApiKeys = false, forceRefund) {
1091
+ async refundXcashuTokens(mintUrl, excludeBaseUrls) {
1115
1092
  const results = [];
1116
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1117
- const toRefund = pendingDistribution.filter(
1118
- (p) => baseUrls.includes(p.baseUrl)
1119
- );
1120
- const refundResults = await Promise.allSettled(
1121
- toRefund.map(async (pending) => {
1122
- const token = this.storageAdapter.getToken(pending.baseUrl);
1123
- this._log("DEBUG", token, this.balanceManager);
1124
- if (!token || !this.balanceManager) {
1125
- return { baseUrl: pending.baseUrl, success: false };
1126
- }
1127
- const tokenBalance = await this.balanceManager.getTokenBalance(
1128
- token,
1129
- pending.baseUrl
1130
- );
1131
- if (tokenBalance.reserved > 0) {
1132
- return { baseUrl: pending.baseUrl, success: false };
1133
- }
1134
- const result = await this.balanceManager.refund({
1135
- mintUrl,
1136
- baseUrl: pending.baseUrl,
1137
- token
1138
- });
1139
- this._log("DEBUG", result);
1140
- if (result.success) {
1141
- this.storageAdapter.removeToken(pending.baseUrl);
1142
- }
1143
- return { baseUrl: pending.baseUrl, success: result.success };
1144
- })
1145
- );
1146
- results.push(
1147
- ...refundResults.map(
1148
- (r) => r.status === "fulfilled" ? r.value : { baseUrl: "", success: false }
1149
- )
1150
- );
1151
- if (refundApiKeys) {
1152
- const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1153
- const apiKeysToRefund = apiKeyDistribution.filter(
1154
- (p) => baseUrls.includes(p.baseUrl)
1155
- );
1156
- for (const apiKeyEntry of apiKeysToRefund) {
1157
- const apiKeyEntryFull = this.storageAdapter.getApiKey(
1158
- apiKeyEntry.baseUrl
1159
- );
1160
- if (apiKeyEntryFull && this.balanceManager) {
1161
- const refundResult = await this.balanceManager.refundApiKey({
1162
- mintUrl,
1163
- baseUrl: apiKeyEntry.baseUrl,
1164
- apiKey: apiKeyEntryFull.key,
1165
- forceRefund
1166
- });
1167
- if (refundResult.success) {
1168
- this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
1093
+ const xcashuTokens = this.storageAdapter.getXcashuTokens();
1094
+ const excludedUrls = new Set(excludeBaseUrls || []);
1095
+ for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
1096
+ if (excludedUrls.has(baseUrl)) continue;
1097
+ for (const xcashuToken of tokens) {
1098
+ try {
1099
+ if (!this.balanceManager) {
1100
+ throw new Error("BalanceManager not available for xcashu refund");
1101
+ }
1102
+ const fetchResult = await this.balanceManager.fetchRefundToken(
1103
+ baseUrl,
1104
+ xcashuToken.token,
1105
+ true
1106
+ );
1107
+ if (!fetchResult.success || !fetchResult.token) {
1108
+ throw new Error(
1109
+ fetchResult.error || "Failed to fetch refund token from provider"
1110
+ );
1111
+ }
1112
+ const receiveResult = await this.receiveToken(fetchResult.token);
1113
+ if (receiveResult.success) {
1114
+ this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
1115
+ results.push({
1116
+ baseUrl,
1117
+ token: xcashuToken.token,
1118
+ success: true
1119
+ });
1120
+ this._log(
1121
+ "DEBUG",
1122
+ `[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
1123
+ );
1124
+ } else {
1125
+ const currentTryCount = xcashuToken.tryCount ?? 0;
1126
+ const newTryCount = currentTryCount + 1;
1127
+ this.storageAdapter.updateXcashuTokenTryCount(
1128
+ xcashuToken.token,
1129
+ newTryCount
1130
+ );
1131
+ results.push({
1132
+ baseUrl,
1133
+ token: xcashuToken.token,
1134
+ success: false,
1135
+ error: receiveResult.message ?? "Refund failed"
1136
+ });
1137
+ this._log(
1138
+ "DEBUG",
1139
+ `[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}`
1140
+ );
1169
1141
  }
1142
+ } catch (error) {
1143
+ const currentTryCount = xcashuToken.tryCount ?? 0;
1144
+ const newTryCount = currentTryCount + 1;
1145
+ this.storageAdapter.updateXcashuTokenTryCount(
1146
+ xcashuToken.token,
1147
+ newTryCount
1148
+ );
1149
+ const errorMessage = error instanceof Error ? error.message : String(error);
1170
1150
  results.push({
1171
- baseUrl: apiKeyEntry.baseUrl,
1172
- success: refundResult.success
1151
+ baseUrl,
1152
+ token: xcashuToken.token,
1153
+ success: false,
1154
+ error: errorMessage
1173
1155
  });
1156
+ this._log(
1157
+ "ERROR",
1158
+ `[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
1159
+ );
1160
+ }
1161
+ }
1162
+ }
1163
+ return results;
1164
+ }
1165
+ /**
1166
+ * Refund specific providers without retrying spend
1167
+ */
1168
+ async refundProviders(mintUrl, forceRefund) {
1169
+ const results = [];
1170
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1171
+ for (const apiKeyEntry of apiKeyDistribution) {
1172
+ const apiKeyEntryFull = this.storageAdapter.getApiKey(
1173
+ apiKeyEntry.baseUrl
1174
+ );
1175
+ if (apiKeyEntryFull && this.balanceManager) {
1176
+ const refundResult = await this.balanceManager.refundApiKey({
1177
+ mintUrl,
1178
+ baseUrl: apiKeyEntry.baseUrl,
1179
+ apiKey: apiKeyEntryFull.key,
1180
+ forceRefund
1181
+ });
1182
+ if (refundResult.success) {
1183
+ this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
1174
1184
  } else {
1175
- results.push({
1176
- baseUrl: apiKeyEntry.baseUrl,
1177
- success: false
1178
- });
1185
+ this.storageAdapter.updateApiKeyBalance(
1186
+ apiKeyEntry.baseUrl,
1187
+ apiKeyEntry.amount
1188
+ );
1179
1189
  }
1190
+ results.push({
1191
+ baseUrl: apiKeyEntry.baseUrl,
1192
+ success: refundResult.success
1193
+ });
1194
+ } else {
1195
+ results.push({
1196
+ baseUrl: apiKeyEntry.baseUrl,
1197
+ success: false
1198
+ });
1180
1199
  }
1181
1200
  }
1182
1201
  return results;
@@ -1261,13 +1280,8 @@ var BalanceManager = class {
1261
1280
  normalizedMintBalances[url] = balanceInSats;
1262
1281
  totalMintBalance += balanceInSats;
1263
1282
  }
1264
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1265
1283
  const providerBalances = {};
1266
1284
  let totalProviderBalance = 0;
1267
- for (const pending of pendingDistribution) {
1268
- providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
1269
- totalProviderBalance += pending.amount;
1270
- }
1271
1285
  const apiKeys = this.storageAdapter.getAllApiKeys();
1272
1286
  for (const apiKey of apiKeys) {
1273
1287
  if (!providerBalances[apiKey.baseUrl]) {
@@ -1282,57 +1296,6 @@ var BalanceManager = class {
1282
1296
  mintBalances: normalizedMintBalances
1283
1297
  };
1284
1298
  }
1285
- /**
1286
- * Unified refund - handles both NIP-60 and legacy wallet refunds
1287
- */
1288
- async refund(options) {
1289
- const { mintUrl, baseUrl, token: providedToken } = options;
1290
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
1291
- if (!storedToken) {
1292
- console.log("[BalanceManager] No token to refund, returning early");
1293
- return { success: true, message: "No API key to refund" };
1294
- }
1295
- let fetchResult;
1296
- try {
1297
- fetchResult = await this._fetchRefundToken(baseUrl, storedToken);
1298
- if (!fetchResult.success) {
1299
- return {
1300
- success: false,
1301
- message: fetchResult.error || "Refund failed",
1302
- requestId: fetchResult.requestId
1303
- };
1304
- }
1305
- if (!fetchResult.token) {
1306
- return {
1307
- success: false,
1308
- message: "No token received from refund",
1309
- requestId: fetchResult.requestId
1310
- };
1311
- }
1312
- if (fetchResult.error === "No balance to refund") {
1313
- console.log(
1314
- "[BalanceManager] No balance to refund, removing stored token"
1315
- );
1316
- this.storageAdapter.removeToken(baseUrl);
1317
- return { success: true, message: "No balance to refund" };
1318
- }
1319
- const receiveResult = await this.cashuSpender.receiveToken(
1320
- fetchResult.token
1321
- );
1322
- const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
1323
- if (!providedToken) {
1324
- this.storageAdapter.removeToken(baseUrl);
1325
- }
1326
- return {
1327
- success: receiveResult.success,
1328
- refundedAmount: totalAmountMsat,
1329
- requestId: fetchResult.requestId
1330
- };
1331
- } catch (error) {
1332
- console.error("[BalanceManager] Refund error", error);
1333
- return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
1334
- }
1335
- }
1336
1299
  /**
1337
1300
  * Refund API key balance - convert remaining API key balance to cashu token
1338
1301
  * @param options - Refund options including forceRefund flag
@@ -1360,7 +1323,7 @@ var BalanceManager = class {
1360
1323
  }
1361
1324
  let fetchResult;
1362
1325
  try {
1363
- fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
1326
+ fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
1364
1327
  if (!fetchResult.success) {
1365
1328
  return {
1366
1329
  success: false,
@@ -1396,9 +1359,9 @@ var BalanceManager = class {
1396
1359
  }
1397
1360
  }
1398
1361
  /**
1399
- * Fetch refund token from provider API using API key authentication
1362
+ * Fetch refund token from provider API using API key (or xcashu token) authentication
1400
1363
  */
1401
- async _fetchRefundTokenWithApiKey(baseUrl, apiKey) {
1364
+ async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
1402
1365
  if (!baseUrl) {
1403
1366
  return {
1404
1367
  success: false,
@@ -1412,12 +1375,17 @@ var BalanceManager = class {
1412
1375
  controller.abort();
1413
1376
  }, 6e4);
1414
1377
  try {
1378
+ const headers = {
1379
+ "Content-Type": "application/json"
1380
+ };
1381
+ if (xCashu) {
1382
+ headers["X-Cashu"] = apiKeyOrToken;
1383
+ } else {
1384
+ headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
1385
+ }
1415
1386
  const response = await fetch(url, {
1416
1387
  method: "POST",
1417
- headers: {
1418
- Authorization: `Bearer ${apiKey}`,
1419
- "Content-Type": "application/json"
1420
- },
1388
+ headers,
1421
1389
  signal: controller.signal
1422
1390
  });
1423
1391
  clearTimeout(timeoutId);
@@ -1438,10 +1406,7 @@ var BalanceManager = class {
1438
1406
  };
1439
1407
  } catch (error) {
1440
1408
  clearTimeout(timeoutId);
1441
- console.error(
1442
- "[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
1443
- error
1444
- );
1409
+ console.error("[BalanceManager.fetchRefundToken] Fetch error", error);
1445
1410
  if (error instanceof Error) {
1446
1411
  if (error.name === "AbortError") {
1447
1412
  return {
@@ -1468,8 +1433,9 @@ var BalanceManager = class {
1468
1433
  if (!amount || amount <= 0) {
1469
1434
  return { success: false, message: "Invalid top up amount" };
1470
1435
  }
1471
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
1472
- if (!storedToken) {
1436
+ const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
1437
+ const apiKey = providedToken || apiKeyEntry?.key;
1438
+ if (!apiKey) {
1473
1439
  return { success: false, message: "No API key available for top up" };
1474
1440
  }
1475
1441
  let cashuToken = null;
@@ -1487,11 +1453,7 @@ var BalanceManager = class {
1487
1453
  };
1488
1454
  }
1489
1455
  cashuToken = tokenResult.token;
1490
- const topUpResult = await this._postTopUp(
1491
- baseUrl,
1492
- storedToken,
1493
- cashuToken
1494
- );
1456
+ const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
1495
1457
  requestId = topUpResult.requestId;
1496
1458
  console.log(topUpResult);
1497
1459
  if (!topUpResult.success) {
@@ -1704,38 +1666,11 @@ var BalanceManager = class {
1704
1666
  return candidates;
1705
1667
  }
1706
1668
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
1707
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1708
1669
  const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1709
1670
  const forceRefund = retryCount >= 2;
1710
- const toRefund = pendingDistribution.filter(
1711
- (pending) => pending.baseUrl !== baseUrl
1712
- );
1713
1671
  const apiKeysToRefund = apiKeyDistribution.filter(
1714
1672
  (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1715
1673
  );
1716
- const tokenRefundResults = await Promise.allSettled(
1717
- toRefund.map(async (pending) => {
1718
- const token = this.storageAdapter.getToken(pending.baseUrl);
1719
- if (!token) {
1720
- return { baseUrl: pending.baseUrl, success: false };
1721
- }
1722
- const tokenBalance = await this.getTokenBalance(token, pending.baseUrl);
1723
- if (tokenBalance.reserved > 0) {
1724
- return { baseUrl: pending.baseUrl, success: false };
1725
- }
1726
- const result = await this.refund({
1727
- mintUrl,
1728
- baseUrl: pending.baseUrl,
1729
- token
1730
- });
1731
- return { baseUrl: pending.baseUrl, success: result.success };
1732
- })
1733
- );
1734
- for (const result of tokenRefundResults) {
1735
- if (result.status === "fulfilled" && result.value.success) {
1736
- this.storageAdapter.removeToken(result.value.baseUrl);
1737
- }
1738
- }
1739
1674
  const apiKeyRefundResults = await Promise.allSettled(
1740
1675
  apiKeysToRefund.map(async (apiKeyEntry) => {
1741
1676
  const fullApiKeyEntry = this.storageAdapter.getApiKey(
@@ -1759,77 +1694,6 @@ var BalanceManager = class {
1759
1694
  }
1760
1695
  }
1761
1696
  }
1762
- /**
1763
- * Fetch refund token from provider API
1764
- */
1765
- async _fetchRefundToken(baseUrl, storedToken) {
1766
- if (!baseUrl) {
1767
- return {
1768
- success: false,
1769
- error: "No base URL configured"
1770
- };
1771
- }
1772
- const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1773
- const url = `${normalizedBaseUrl}v1/wallet/refund`;
1774
- const controller = new AbortController();
1775
- const timeoutId = setTimeout(() => {
1776
- controller.abort();
1777
- }, 6e4);
1778
- try {
1779
- const response = await fetch(url, {
1780
- method: "POST",
1781
- headers: {
1782
- Authorization: `Bearer ${storedToken}`,
1783
- "Content-Type": "application/json"
1784
- },
1785
- signal: controller.signal
1786
- });
1787
- clearTimeout(timeoutId);
1788
- const requestId = response.headers.get("x-routstr-request-id") || void 0;
1789
- if (!response.ok) {
1790
- const errorData = await response.json().catch(() => ({}));
1791
- if (response.status === 400 && errorData?.detail === "No balance to refund") {
1792
- this.storageAdapter.removeToken(baseUrl);
1793
- return {
1794
- success: false,
1795
- requestId,
1796
- error: "No balance to refund"
1797
- };
1798
- }
1799
- return {
1800
- success: false,
1801
- requestId,
1802
- error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
1803
- };
1804
- }
1805
- const data = await response.json();
1806
- console.log("refund rsule", data);
1807
- return {
1808
- success: true,
1809
- token: data.token,
1810
- requestId
1811
- };
1812
- } catch (error) {
1813
- clearTimeout(timeoutId);
1814
- console.error("[BalanceManager._fetchRefundToken] Fetch error", error);
1815
- if (error instanceof Error) {
1816
- if (error.name === "AbortError") {
1817
- return {
1818
- success: false,
1819
- error: "Request timed out after 1 minute"
1820
- };
1821
- }
1822
- return {
1823
- success: false,
1824
- error: error.message
1825
- };
1826
- }
1827
- return {
1828
- success: false,
1829
- error: "Unknown error occurred during refund request"
1830
- };
1831
- }
1832
- }
1833
1697
  /**
1834
1698
  * Post topup request to provider API
1835
1699
  */
@@ -1955,7 +1819,7 @@ var BalanceManager = class {
1955
1819
  console.log(response.status);
1956
1820
  const data = await response.json();
1957
1821
  console.log("FAILED ", data);
1958
- const isInvalidApiKey = response.status === 401 && data?.code === "invalid_api_key" && data?.message?.includes("proofs already spent");
1822
+ const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
1959
1823
  return {
1960
1824
  amount: -1,
1961
1825
  reserved: data.reserved ?? 0,
@@ -2453,8 +2317,13 @@ function isInsecureHttpUrl(url) {
2453
2317
  return url.startsWith("http://");
2454
2318
  }
2455
2319
  var ProviderManager = class _ProviderManager {
2456
- constructor(providerRegistry) {
2320
+ constructor(providerRegistry, store) {
2457
2321
  this.providerRegistry = providerRegistry;
2322
+ this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
2323
+ if (store) {
2324
+ this.store = store;
2325
+ this.hydrateFromStore();
2326
+ }
2458
2327
  }
2459
2328
  failedProviders = /* @__PURE__ */ new Set();
2460
2329
  /** Track when each provider last failed (provider URL -> timestamp) */
@@ -2463,14 +2332,57 @@ var ProviderManager = class _ProviderManager {
2463
2332
  providersOnCoolDown = [];
2464
2333
  /** Cooldown duration in milliseconds (5 minutes) */
2465
2334
  static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
2335
+ /** Optional persistent store for failure tracking */
2336
+ store = null;
2337
+ /** Instance ID for debugging */
2338
+ instanceId;
2339
+ /**
2340
+ * Hydrate in-memory state from persistent store
2341
+ */
2342
+ hydrateFromStore() {
2343
+ if (!this.store) return;
2344
+ const state = this.store.getState();
2345
+ this.failedProviders = new Set(state.failedProviders);
2346
+ this.lastFailed = new Map(Object.entries(state.lastFailed));
2347
+ const now = Date.now();
2348
+ this.providersOnCoolDown = state.providersOnCooldown.filter(
2349
+ (entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
2350
+ ).map((entry) => [entry.baseUrl, entry.timestamp]);
2351
+ console.log(`[ProviderManager:${this.instanceId}] Hydrated from store:`);
2352
+ console.log(` failedProviders: ${this.failedProviders.size}`);
2353
+ console.log(` lastFailed: ${this.lastFailed.size}`);
2354
+ console.log(` providersOnCooldown: ${this.providersOnCoolDown.length}`);
2355
+ }
2356
+ /**
2357
+ * Get instance ID for debugging
2358
+ */
2359
+ getInstanceId() {
2360
+ return this.instanceId;
2361
+ }
2466
2362
  /**
2467
2363
  * Clean up expired cooldown entries
2468
2364
  */
2469
2365
  cleanupExpiredCooldowns() {
2470
2366
  const now = Date.now();
2367
+ const before = this.providersOnCoolDown.length;
2471
2368
  this.providersOnCoolDown = this.providersOnCoolDown.filter(
2472
- ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
2369
+ ([url, timestamp]) => {
2370
+ const age = now - timestamp;
2371
+ const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
2372
+ if (isExpired) {
2373
+ console.log(
2374
+ `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
2375
+ );
2376
+ }
2377
+ return !isExpired;
2378
+ }
2473
2379
  );
2380
+ const after = this.providersOnCoolDown.length;
2381
+ if (before !== after) {
2382
+ console.log(
2383
+ `[cleanupExpiredCooldowns:${this.instanceId}] Cleaned up ${before - after} expired cooldown(s), ${after} remaining`
2384
+ );
2385
+ }
2474
2386
  }
2475
2387
  /**
2476
2388
  * Get the cooldown duration in milliseconds
@@ -2483,7 +2395,8 @@ var ProviderManager = class _ProviderManager {
2483
2395
  */
2484
2396
  isOnCooldown(baseUrl) {
2485
2397
  this.cleanupExpiredCooldowns();
2486
- return this.providersOnCoolDown.some(([url]) => url === baseUrl);
2398
+ const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
2399
+ return result;
2487
2400
  }
2488
2401
  /**
2489
2402
  * Get all providers currently on cooldown
@@ -2497,6 +2410,9 @@ var ProviderManager = class _ProviderManager {
2497
2410
  */
2498
2411
  resetFailedProviders() {
2499
2412
  this.failedProviders.clear();
2413
+ if (this.store) {
2414
+ this.store.getState().setFailedProviders([]);
2415
+ }
2500
2416
  }
2501
2417
  /**
2502
2418
  * Get the last failed timestamp for a provider
@@ -2517,13 +2433,62 @@ var ProviderManager = class _ProviderManager {
2517
2433
  markFailed(baseUrl) {
2518
2434
  const now = Date.now();
2519
2435
  const lastFailure = this.lastFailed.get(baseUrl);
2436
+ console.log(`[markFailed:${this.instanceId}] baseUrl: ${baseUrl}`);
2437
+ console.log(
2438
+ `[markFailed:${this.instanceId}] lastFailure from map: ${lastFailure}`
2439
+ );
2440
+ console.log(
2441
+ `[markFailed:${this.instanceId}] current timestamp (now): ${now}`
2442
+ );
2443
+ console.log(
2444
+ `[markFailed:${this.instanceId}] COOLDOWN_DURATION_MS: ${_ProviderManager.COOLDOWN_DURATION_MS}`
2445
+ );
2446
+ if (lastFailure !== void 0) {
2447
+ const timeSinceLastFailure = now - lastFailure;
2448
+ console.log(
2449
+ `[markFailed:${this.instanceId}] timeSinceLastFailure: ${timeSinceLastFailure}ms`
2450
+ );
2451
+ console.log(
2452
+ `[markFailed:${this.instanceId}] isWithinCooldownWindow: ${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`
2453
+ );
2454
+ }
2520
2455
  this.lastFailed.set(baseUrl, now);
2521
2456
  this.failedProviders.add(baseUrl);
2457
+ if (this.store) {
2458
+ this.store.getState().setLastFailedTimestamp(baseUrl, now);
2459
+ this.store.getState().addFailedProvider(baseUrl);
2460
+ }
2461
+ console.log(
2462
+ `[markFailed:${this.instanceId}] Updated lastFailed map for ${baseUrl} to ${now}`
2463
+ );
2464
+ console.log(
2465
+ `[markFailed:${this.instanceId}] failedProviders set size: ${this.failedProviders.size}`
2466
+ );
2522
2467
  if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
2468
+ console.log(
2469
+ `[markFailed:${this.instanceId}] Second failure detected within cooldown window for ${baseUrl}`
2470
+ );
2523
2471
  if (!this.isOnCooldown(baseUrl)) {
2524
2472
  this.providersOnCoolDown.push([baseUrl, now]);
2473
+ if (this.store) {
2474
+ this.store.getState().addProviderOnCooldown(baseUrl, now);
2475
+ }
2476
+ console.log(
2477
+ `[markFailed:${this.instanceId}] Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
2478
+ );
2479
+ } else {
2480
+ console.log(
2481
+ `[markFailed:${this.instanceId}] Provider ${baseUrl} is already on cooldown`
2482
+ );
2483
+ }
2484
+ } else {
2485
+ if (lastFailure === void 0) {
2486
+ console.log(
2487
+ `[markFailed:${this.instanceId}] First failure for ${baseUrl} - not adding to cooldown yet`
2488
+ );
2489
+ } else {
2525
2490
  console.log(
2526
- `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
2491
+ `[markFailed:${this.instanceId}] Failure outside cooldown window for ${baseUrl} (timeSinceLastFailure: ${now - lastFailure}ms)`
2527
2492
  );
2528
2493
  }
2529
2494
  }
@@ -2535,18 +2500,27 @@ var ProviderManager = class _ProviderManager {
2535
2500
  this.providersOnCoolDown = this.providersOnCoolDown.filter(
2536
2501
  ([url]) => url !== baseUrl
2537
2502
  );
2503
+ if (this.store) {
2504
+ this.store.getState().removeProviderFromCooldown(baseUrl);
2505
+ }
2538
2506
  }
2539
2507
  /**
2540
2508
  * Clear all cooldown tracking
2541
2509
  */
2542
2510
  clearCooldowns() {
2543
2511
  this.providersOnCoolDown = [];
2512
+ if (this.store) {
2513
+ this.store.getState().clearProvidersOnCooldown();
2514
+ }
2544
2515
  }
2545
2516
  /**
2546
2517
  * Clear all failure tracking (lastFailed timestamps)
2547
2518
  */
2548
2519
  clearFailureHistory() {
2549
2520
  this.lastFailed.clear();
2521
+ if (this.store) {
2522
+ this.store.getState().setLastFailed({});
2523
+ }
2550
2524
  }
2551
2525
  /**
2552
2526
  * Check if a provider has failed
@@ -2874,38 +2848,54 @@ var createMemoryDriver = (seed) => {
2874
2848
  var isBun = () => {
2875
2849
  return typeof process.versions.bun !== "undefined";
2876
2850
  };
2877
- var createDatabase = (dbPath) => {
2851
+ var cachedDbModule = null;
2852
+ var loadDatabase = async (dbPath) => {
2878
2853
  if (isBun()) {
2879
2854
  throw new Error(
2880
- "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
2855
+ "SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
2881
2856
  );
2882
2857
  }
2883
- let Database = null;
2884
2858
  try {
2885
- Database = __require("better-sqlite3");
2859
+ if (!cachedDbModule) {
2860
+ cachedDbModule = (await import('better-sqlite3')).default;
2861
+ }
2862
+ return new cachedDbModule(dbPath);
2886
2863
  } catch (error) {
2887
2864
  throw new Error(
2888
2865
  `better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
2889
2866
  );
2890
2867
  }
2891
- return new Database(dbPath);
2892
2868
  };
2893
2869
  var createSqliteDriver = (options = {}) => {
2894
2870
  const dbPath = options.dbPath || "routstr.sqlite";
2895
2871
  const tableName = options.tableName || "sdk_storage";
2896
- const db = createDatabase(dbPath);
2897
- db.exec(
2898
- `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
2899
- );
2900
- const selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
2901
- const upsertStmt = db.prepare(
2902
- `INSERT INTO ${tableName} (key, value) VALUES (?, ?)
2872
+ let db;
2873
+ let selectStmt;
2874
+ let upsertStmt;
2875
+ let deleteStmt;
2876
+ const initDb = async () => {
2877
+ if (!db) {
2878
+ db = await loadDatabase(dbPath);
2879
+ db.exec(
2880
+ `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
2881
+ );
2882
+ selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
2883
+ upsertStmt = db.prepare(
2884
+ `INSERT INTO ${tableName} (key, value) VALUES (?, ?)
2903
2885
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
2904
- );
2905
- const deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2886
+ );
2887
+ deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2888
+ }
2889
+ };
2890
+ const ensureInit = async () => {
2891
+ if (!db) {
2892
+ await initDb();
2893
+ }
2894
+ };
2906
2895
  return {
2907
2896
  async getItem(key, defaultValue) {
2908
2897
  try {
2898
+ await ensureInit();
2909
2899
  const row = selectStmt.get(key);
2910
2900
  if (!row || typeof row.value !== "string") return defaultValue;
2911
2901
  try {
@@ -2923,6 +2913,7 @@ var createSqliteDriver = (options = {}) => {
2923
2913
  },
2924
2914
  async setItem(key, value) {
2925
2915
  try {
2916
+ await ensureInit();
2926
2917
  upsertStmt.run(key, JSON.stringify(value));
2927
2918
  } catch (error) {
2928
2919
  console.error(`SQLite setItem failed for key "${key}":`, error);
@@ -2930,6 +2921,7 @@ var createSqliteDriver = (options = {}) => {
2930
2921
  },
2931
2922
  async removeItem(key) {
2932
2923
  try {
2924
+ await ensureInit();
2933
2925
  deleteStmt.run(key);
2934
2926
  } catch (error) {
2935
2927
  console.error(`SQLite removeItem failed for key "${key}":`, error);
@@ -2937,6 +2929,54 @@ var createSqliteDriver = (options = {}) => {
2937
2929
  }
2938
2930
  };
2939
2931
  };
2932
+ async function createBunSqliteDriver(dbPath) {
2933
+ const SQLite = (await import(
2934
+ /* webpackIgnore: true */
2935
+ 'bun:sqlite'
2936
+ )).default;
2937
+ const db = new SQLite(dbPath);
2938
+ db.run(`
2939
+ CREATE TABLE IF NOT EXISTS sdk_storage (
2940
+ key TEXT PRIMARY KEY,
2941
+ value TEXT NOT NULL
2942
+ )
2943
+ `);
2944
+ return {
2945
+ async getItem(key, defaultValue) {
2946
+ try {
2947
+ const row = db.query("SELECT value FROM sdk_storage WHERE key = ?").get(key);
2948
+ if (!row || typeof row.value !== "string") return defaultValue;
2949
+ try {
2950
+ return JSON.parse(row.value);
2951
+ } catch (parseError) {
2952
+ if (typeof defaultValue === "string") {
2953
+ return row.value;
2954
+ }
2955
+ throw parseError;
2956
+ }
2957
+ } catch (error) {
2958
+ console.error(`SQLite getItem failed for key "${key}":`, error);
2959
+ return defaultValue;
2960
+ }
2961
+ },
2962
+ async setItem(key, value) {
2963
+ try {
2964
+ db.query(
2965
+ "INSERT INTO sdk_storage (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
2966
+ ).run(key, JSON.stringify(value));
2967
+ } catch (error) {
2968
+ console.error(`SQLite setItem failed for key "${key}":`, error);
2969
+ }
2970
+ },
2971
+ async removeItem(key) {
2972
+ try {
2973
+ db.query("DELETE FROM sdk_storage WHERE key = ?").run(key);
2974
+ } catch (error) {
2975
+ console.error(`SQLite removeItem failed for key "${key}":`, error);
2976
+ }
2977
+ }
2978
+ };
2979
+ }
2940
2980
 
2941
2981
  // storage/drivers/indexedDB.ts
2942
2982
  var isBrowser = typeof indexedDB !== "undefined";
@@ -3042,14 +3082,17 @@ var SDK_STORAGE_KEYS = {
3042
3082
  INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
3043
3083
  LAST_MODELS_UPDATE: "lastModelsUpdate",
3044
3084
  LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
3045
- LOCAL_CASHU_TOKENS: "local_cashu_tokens",
3046
3085
  API_KEYS: "api_keys",
3047
3086
  CHILD_KEYS: "child_keys",
3087
+ XCASHU_TOKENS: "xcashu_tokens",
3048
3088
  ROUTSTR21_MODELS: "routstr21Models",
3049
3089
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
3050
3090
  CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
3051
3091
  USAGE_TRACKING: "usage_tracking",
3052
- CLIENT_IDS: "client_ids"
3092
+ CLIENT_IDS: "client_ids",
3093
+ FAILED_PROVIDERS: "failed_providers",
3094
+ LAST_FAILED: "last_failed",
3095
+ PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
3053
3096
  };
3054
3097
 
3055
3098
  // storage/usageTracking/indexedDB.ts
@@ -3235,21 +3278,23 @@ var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUr
3235
3278
  var isBun2 = () => {
3236
3279
  return typeof process.versions.bun !== "undefined";
3237
3280
  };
3238
- var createDatabase2 = (dbPath) => {
3281
+ var cachedDbModule2 = null;
3282
+ var loadDatabase2 = async (dbPath) => {
3239
3283
  if (isBun2()) {
3240
3284
  throw new Error(
3241
3285
  "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
3242
3286
  );
3243
3287
  }
3244
- let Database = null;
3245
3288
  try {
3246
- Database = __require("better-sqlite3");
3289
+ if (!cachedDbModule2) {
3290
+ cachedDbModule2 = (await import('better-sqlite3')).default;
3291
+ }
3292
+ return new cachedDbModule2(dbPath);
3247
3293
  } catch (error) {
3248
3294
  throw new Error(
3249
3295
  `better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
3250
3296
  );
3251
3297
  }
3252
- return new Database(dbPath);
3253
3298
  };
3254
3299
  var buildWhereClause = (options = {}) => {
3255
3300
  const clauses = [];
@@ -3286,38 +3331,49 @@ var buildWhereClause = (options = {}) => {
3286
3331
  var createSqliteUsageTrackingDriver = (options = {}) => {
3287
3332
  const dbPath = options.dbPath || "routstr.sqlite";
3288
3333
  const tableName = options.tableName || "usage_tracking";
3289
- const db = createDatabase2(dbPath);
3290
3334
  const legacyStorageDriver = options.legacyStorageDriver;
3291
- db.exec(`
3292
- CREATE TABLE IF NOT EXISTS ${tableName} (
3293
- id TEXT PRIMARY KEY,
3294
- timestamp INTEGER NOT NULL,
3295
- model_id TEXT NOT NULL,
3296
- base_url TEXT NOT NULL,
3297
- request_id TEXT NOT NULL,
3298
- cost REAL NOT NULL,
3299
- sats_cost REAL NOT NULL,
3300
- prompt_tokens INTEGER NOT NULL,
3301
- completion_tokens INTEGER NOT NULL,
3302
- total_tokens INTEGER NOT NULL,
3303
- client TEXT,
3304
- session_id TEXT,
3305
- tags TEXT
3306
- );
3307
- CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
3308
- CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
3309
- CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
3310
- CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
3311
- CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
3312
- `);
3313
- const insertStmt = db.prepare(`
3314
- INSERT OR REPLACE INTO ${tableName} (
3315
- id, timestamp, model_id, base_url, request_id,
3316
- cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
3317
- client, session_id, tags
3318
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3319
- `);
3335
+ let db;
3336
+ let insertStmt;
3320
3337
  let migrationComplete = false;
3338
+ const initDb = async () => {
3339
+ if (!db) {
3340
+ db = await loadDatabase2(dbPath);
3341
+ db.exec(`
3342
+ CREATE TABLE IF NOT EXISTS ${tableName} (
3343
+ id TEXT PRIMARY KEY,
3344
+ timestamp INTEGER NOT NULL,
3345
+ model_id TEXT NOT NULL,
3346
+ base_url TEXT NOT NULL,
3347
+ request_id TEXT NOT NULL,
3348
+ cost REAL NOT NULL,
3349
+ sats_cost REAL NOT NULL,
3350
+ prompt_tokens INTEGER NOT NULL,
3351
+ completion_tokens INTEGER NOT NULL,
3352
+ total_tokens INTEGER NOT NULL,
3353
+ client TEXT,
3354
+ session_id TEXT,
3355
+ tags TEXT
3356
+ );
3357
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
3358
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
3359
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
3360
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
3361
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
3362
+ `);
3363
+ insertStmt = db.prepare(`
3364
+ INSERT OR REPLACE INTO ${tableName} (
3365
+ id, timestamp, model_id, base_url, request_id,
3366
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
3367
+ client, session_id, tags
3368
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3369
+ `);
3370
+ }
3371
+ };
3372
+ const ensureInit = async () => {
3373
+ if (!db) {
3374
+ await initDb();
3375
+ }
3376
+ };
3321
3377
  const appendOne = (entry) => {
3322
3378
  insertStmt.run(
3323
3379
  entry.id,
@@ -3375,19 +3431,23 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
3375
3431
  });
3376
3432
  return {
3377
3433
  async migrate() {
3434
+ await ensureInit();
3378
3435
  await ensureMigrated();
3379
3436
  },
3380
3437
  async append(entry) {
3438
+ await ensureInit();
3381
3439
  await ensureMigrated();
3382
3440
  appendOne(entry);
3383
3441
  },
3384
3442
  async appendMany(entries) {
3443
+ await ensureInit();
3385
3444
  await ensureMigrated();
3386
3445
  for (const entry of entries) {
3387
3446
  appendOne(entry);
3388
3447
  }
3389
3448
  },
3390
3449
  async list(options2 = {}) {
3450
+ await ensureInit();
3391
3451
  await ensureMigrated();
3392
3452
  const { sql, params } = buildWhereClause(options2);
3393
3453
  const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
@@ -3400,6 +3460,7 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
3400
3460
  return rows.map(mapRow);
3401
3461
  },
3402
3462
  async count(options2 = {}) {
3463
+ await ensureInit();
3403
3464
  await ensureMigrated();
3404
3465
  const { sql, params } = buildWhereClause(options2);
3405
3466
  const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
@@ -3407,20 +3468,197 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
3407
3468
  return Number(row?.count ?? 0);
3408
3469
  },
3409
3470
  async deleteOlderThan(timestamp) {
3471
+ await ensureInit();
3410
3472
  await ensureMigrated();
3411
3473
  const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
3412
3474
  const result = stmt.run(timestamp);
3413
3475
  return result.changes;
3414
3476
  },
3415
3477
  async clear() {
3478
+ await ensureInit();
3416
3479
  await ensureMigrated();
3417
3480
  db.prepare(`DELETE FROM ${tableName}`).run();
3418
3481
  }
3419
3482
  };
3420
3483
  };
3421
3484
 
3422
- // storage/usageTracking/memory.ts
3485
+ // storage/usageTracking/bunSqlite.ts
3486
+ var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
3423
3487
  var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3488
+ var buildWhereClause2 = (options = {}) => {
3489
+ const clauses = [];
3490
+ const params = [];
3491
+ if (typeof options.before === "number") {
3492
+ clauses.push("timestamp < ?");
3493
+ params.push(options.before);
3494
+ }
3495
+ if (typeof options.after === "number") {
3496
+ clauses.push("timestamp > ?");
3497
+ params.push(options.after);
3498
+ }
3499
+ if (options.modelId) {
3500
+ clauses.push("model_id = ?");
3501
+ params.push(options.modelId);
3502
+ }
3503
+ if (options.baseUrl) {
3504
+ clauses.push("base_url = ?");
3505
+ params.push(normalizeBaseUrl3(options.baseUrl));
3506
+ }
3507
+ if (options.sessionId) {
3508
+ clauses.push("session_id = ?");
3509
+ params.push(options.sessionId);
3510
+ }
3511
+ if (options.client) {
3512
+ clauses.push("client = ?");
3513
+ params.push(options.client);
3514
+ }
3515
+ return {
3516
+ sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
3517
+ params
3518
+ };
3519
+ };
3520
+ var createBunSqliteUsageTrackingDriver = (options = {}) => {
3521
+ const dbPath = options.dbPath || "routstr.sqlite";
3522
+ const tableName = options.tableName || "usage_tracking";
3523
+ const legacyStorageDriver = options.legacyStorageDriver;
3524
+ const SQLiteDatabase = options.sqlite?.Database;
3525
+ let migrationPromise = null;
3526
+ if (!SQLiteDatabase) {
3527
+ throw new Error(
3528
+ "Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
3529
+ );
3530
+ }
3531
+ const db = new SQLiteDatabase(dbPath);
3532
+ db.run(`
3533
+ CREATE TABLE IF NOT EXISTS ${tableName} (
3534
+ id TEXT PRIMARY KEY,
3535
+ timestamp INTEGER NOT NULL,
3536
+ model_id TEXT NOT NULL,
3537
+ base_url TEXT NOT NULL,
3538
+ request_id TEXT NOT NULL,
3539
+ cost REAL NOT NULL,
3540
+ sats_cost REAL NOT NULL,
3541
+ prompt_tokens INTEGER NOT NULL,
3542
+ completion_tokens INTEGER NOT NULL,
3543
+ total_tokens INTEGER NOT NULL,
3544
+ client TEXT,
3545
+ session_id TEXT,
3546
+ tags TEXT
3547
+ )
3548
+ `);
3549
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
3550
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
3551
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
3552
+ const appendOne = (entry) => {
3553
+ db.query(`
3554
+ INSERT OR REPLACE INTO ${tableName} (
3555
+ id, timestamp, model_id, base_url, request_id,
3556
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
3557
+ client, session_id, tags
3558
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3559
+ `).run(
3560
+ entry.id,
3561
+ entry.timestamp,
3562
+ entry.modelId,
3563
+ normalizeBaseUrl3(entry.baseUrl),
3564
+ entry.requestId,
3565
+ entry.cost,
3566
+ entry.satsCost,
3567
+ entry.promptTokens,
3568
+ entry.completionTokens,
3569
+ entry.totalTokens,
3570
+ entry.client ?? null,
3571
+ entry.sessionId ?? null,
3572
+ JSON.stringify(entry.tags ?? [])
3573
+ );
3574
+ };
3575
+ const mapRow = (row) => ({
3576
+ id: row.id,
3577
+ timestamp: row.timestamp,
3578
+ modelId: row.model_id,
3579
+ baseUrl: row.base_url,
3580
+ requestId: row.request_id,
3581
+ cost: row.cost,
3582
+ satsCost: row.sats_cost,
3583
+ promptTokens: row.prompt_tokens,
3584
+ completionTokens: row.completion_tokens,
3585
+ totalTokens: row.total_tokens,
3586
+ client: row.client ?? void 0,
3587
+ sessionId: row.session_id ?? void 0,
3588
+ tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
3589
+ });
3590
+ const ensureMigrated = async () => {
3591
+ if (!legacyStorageDriver) return;
3592
+ if (!migrationPromise) {
3593
+ migrationPromise = (async () => {
3594
+ const migrated = await legacyStorageDriver.getItem(
3595
+ MIGRATION_MARKER_KEY3,
3596
+ false
3597
+ );
3598
+ if (migrated) return;
3599
+ const legacyEntries = await legacyStorageDriver.getItem(
3600
+ SDK_STORAGE_KEYS.USAGE_TRACKING,
3601
+ []
3602
+ );
3603
+ if (legacyEntries.length > 0) {
3604
+ for (const entry of legacyEntries) {
3605
+ appendOne(entry);
3606
+ }
3607
+ await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
3608
+ }
3609
+ await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
3610
+ })();
3611
+ }
3612
+ await migrationPromise;
3613
+ };
3614
+ return {
3615
+ async migrate() {
3616
+ await ensureMigrated();
3617
+ },
3618
+ async append(entry) {
3619
+ await ensureMigrated();
3620
+ appendOne(entry);
3621
+ },
3622
+ async appendMany(entries) {
3623
+ await ensureMigrated();
3624
+ for (const entry of entries) {
3625
+ appendOne(entry);
3626
+ }
3627
+ },
3628
+ async list(options2 = {}) {
3629
+ await ensureMigrated();
3630
+ const { sql, params } = buildWhereClause2(options2);
3631
+ const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
3632
+ const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
3633
+ let rows;
3634
+ if (typeof options2.limit === "number") {
3635
+ rows = db.query(query).all(...params, options2.limit);
3636
+ } else {
3637
+ rows = db.query(query).all(...params);
3638
+ }
3639
+ return rows.map(mapRow);
3640
+ },
3641
+ async count(options2 = {}) {
3642
+ const { sql, params } = buildWhereClause2(options2);
3643
+ const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
3644
+ const row = db.query(query).get(...params);
3645
+ return Number(row?.count ?? 0);
3646
+ },
3647
+ async deleteOlderThan(timestamp) {
3648
+ await ensureMigrated();
3649
+ const before = timestamp;
3650
+ const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
3651
+ return result.changes ?? 0;
3652
+ },
3653
+ async clear() {
3654
+ await ensureMigrated();
3655
+ db.query(`DELETE FROM ${tableName}`).run();
3656
+ }
3657
+ };
3658
+ };
3659
+
3660
+ // storage/usageTracking/memory.ts
3661
+ var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3424
3662
  var matchesFilters2 = (entry, options = {}) => {
3425
3663
  if (typeof options.before === "number" && entry.timestamp >= options.before) {
3426
3664
  return false;
@@ -3431,7 +3669,7 @@ var matchesFilters2 = (entry, options = {}) => {
3431
3669
  if (options.modelId && entry.modelId !== options.modelId) {
3432
3670
  return false;
3433
3671
  }
3434
- if (options.baseUrl && normalizeBaseUrl3(entry.baseUrl) !== normalizeBaseUrl3(options.baseUrl)) {
3672
+ if (options.baseUrl && normalizeBaseUrl4(entry.baseUrl) !== normalizeBaseUrl4(options.baseUrl)) {
3435
3673
  return false;
3436
3674
  }
3437
3675
  if (options.sessionId && entry.sessionId !== options.sessionId) {
@@ -3445,18 +3683,18 @@ var matchesFilters2 = (entry, options = {}) => {
3445
3683
  var createMemoryUsageTrackingDriver = (seed = []) => {
3446
3684
  const store = /* @__PURE__ */ new Map();
3447
3685
  for (const entry of seed) {
3448
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
3686
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
3449
3687
  }
3450
3688
  return {
3451
3689
  async migrate() {
3452
3690
  return;
3453
3691
  },
3454
3692
  async append(entry) {
3455
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
3693
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
3456
3694
  },
3457
3695
  async appendMany(entries) {
3458
3696
  for (const entry of entries) {
3459
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
3697
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
3460
3698
  }
3461
3699
  },
3462
3700
  async list(options = {}) {
@@ -3484,20 +3722,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
3484
3722
  }
3485
3723
  };
3486
3724
  };
3487
- var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3488
- var getCashuTokenBalance = (token) => {
3489
- try {
3490
- const decoded = getDecodedToken(token);
3491
- const unitDivisor = decoded.unit === "msat" ? 1e3 : 1;
3492
- let sum = 0;
3493
- for (const proof of decoded.proofs) {
3494
- sum += proof.amount / unitDivisor;
3495
- }
3496
- return sum;
3497
- } catch {
3498
- return 0;
3499
- }
3500
- };
3725
+ var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3501
3726
  var createEmptyStore = (driver) => createStore((set, get) => ({
3502
3727
  modelsFromAllProviders: {},
3503
3728
  lastUsedModel: null,
@@ -3507,17 +3732,20 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3507
3732
  mintsFromAllProviders: {},
3508
3733
  infoFromAllProviders: {},
3509
3734
  lastModelsUpdate: {},
3510
- cachedTokens: [],
3511
3735
  apiKeys: [],
3512
3736
  childKeys: [],
3737
+ xcashuTokens: {},
3513
3738
  routstr21Models: [],
3514
3739
  lastRoutstr21ModelsUpdate: null,
3515
3740
  cachedReceiveTokens: [],
3516
3741
  clientIds: [],
3742
+ failedProviders: [],
3743
+ lastFailed: {},
3744
+ providersOnCooldown: [],
3517
3745
  setModelsFromAllProviders: (value) => {
3518
3746
  const normalized = {};
3519
3747
  for (const [baseUrl, models] of Object.entries(value)) {
3520
- normalized[normalizeBaseUrl4(baseUrl)] = models;
3748
+ normalized[normalizeBaseUrl5(baseUrl)] = models;
3521
3749
  }
3522
3750
  void driver.setItem(
3523
3751
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3530,7 +3758,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3530
3758
  set({ lastUsedModel: value });
3531
3759
  },
3532
3760
  setBaseUrlsList: (value) => {
3533
- const normalized = value.map((url) => normalizeBaseUrl4(url));
3761
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3534
3762
  void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
3535
3763
  set({ baseUrlsList: normalized });
3536
3764
  },
@@ -3539,14 +3767,14 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3539
3767
  set({ lastBaseUrlsUpdate: value });
3540
3768
  },
3541
3769
  setDisabledProviders: (value) => {
3542
- const normalized = value.map((url) => normalizeBaseUrl4(url));
3770
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3543
3771
  void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
3544
3772
  set({ disabledProviders: normalized });
3545
3773
  },
3546
3774
  setMintsFromAllProviders: (value) => {
3547
3775
  const normalized = {};
3548
3776
  for (const [baseUrl, mints] of Object.entries(value)) {
3549
- normalized[normalizeBaseUrl4(baseUrl)] = mints.map(
3777
+ normalized[normalizeBaseUrl5(baseUrl)] = mints.map(
3550
3778
  (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
3551
3779
  );
3552
3780
  }
@@ -3559,7 +3787,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3559
3787
  setInfoFromAllProviders: (value) => {
3560
3788
  const normalized = {};
3561
3789
  for (const [baseUrl, info] of Object.entries(value)) {
3562
- normalized[normalizeBaseUrl4(baseUrl)] = info;
3790
+ normalized[normalizeBaseUrl5(baseUrl)] = info;
3563
3791
  }
3564
3792
  void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
3565
3793
  set({ infoFromAllProviders: normalized });
@@ -3567,30 +3795,17 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3567
3795
  setLastModelsUpdate: (value) => {
3568
3796
  const normalized = {};
3569
3797
  for (const [baseUrl, timestamp] of Object.entries(value)) {
3570
- normalized[normalizeBaseUrl4(baseUrl)] = timestamp;
3798
+ normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
3571
3799
  }
3572
3800
  void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
3573
3801
  set({ lastModelsUpdate: normalized });
3574
3802
  },
3575
- setCachedTokens: (value) => {
3576
- set((state) => {
3577
- const updates = typeof value === "function" ? value(state.cachedTokens) : value;
3578
- const normalized = updates.map((entry) => ({
3579
- ...entry,
3580
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
3581
- balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
3582
- lastUsed: entry.lastUsed ?? null
3583
- }));
3584
- void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
3585
- return { cachedTokens: normalized };
3586
- });
3587
- },
3588
3803
  setApiKeys: (value) => {
3589
3804
  set((state) => {
3590
3805
  const updates = typeof value === "function" ? value(state.apiKeys) : value;
3591
3806
  const normalized = updates.map((entry) => ({
3592
3807
  ...entry,
3593
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
3808
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3594
3809
  balance: entry.balance ?? 0,
3595
3810
  lastUsed: entry.lastUsed ?? null
3596
3811
  }));
@@ -3602,7 +3817,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3602
3817
  set((state) => {
3603
3818
  const updates = typeof value === "function" ? value(state.childKeys) : value;
3604
3819
  const normalized = updates.map((entry) => ({
3605
- parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
3820
+ parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
3606
3821
  childKey: entry.childKey,
3607
3822
  balance: entry.balance ?? 0,
3608
3823
  balanceLimit: entry.balanceLimit,
@@ -3613,6 +3828,30 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3613
3828
  return { childKeys: normalized };
3614
3829
  });
3615
3830
  },
3831
+ setXcashuTokens: (value) => {
3832
+ const normalized = {};
3833
+ for (const [baseUrl, tokens] of Object.entries(value)) {
3834
+ normalized[normalizeBaseUrl5(baseUrl)] = tokens.map((entry) => ({
3835
+ ...entry,
3836
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3837
+ createdAt: entry.createdAt ?? Date.now(),
3838
+ tryCount: entry.tryCount ?? 0
3839
+ }));
3840
+ }
3841
+ void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, normalized);
3842
+ set({ xcashuTokens: normalized });
3843
+ },
3844
+ updateXcashuTokenTryCount: (token, tryCount) => {
3845
+ const currentTokens = get().xcashuTokens;
3846
+ const updatedTokens = {};
3847
+ for (const [baseUrl, tokens] of Object.entries(currentTokens)) {
3848
+ updatedTokens[baseUrl] = tokens.map(
3849
+ (entry) => entry.token === token ? { ...entry, tryCount } : entry
3850
+ );
3851
+ }
3852
+ void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, updatedTokens);
3853
+ set({ xcashuTokens: updatedTokens });
3854
+ },
3616
3855
  setRoutstr21Models: (value) => {
3617
3856
  void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
3618
3857
  set({ routstr21Models: value });
@@ -3642,6 +3881,71 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3642
3881
  void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
3643
3882
  return { clientIds: normalized };
3644
3883
  });
3884
+ },
3885
+ // ========== Failure Tracking ==========
3886
+ setFailedProviders: (value) => {
3887
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3888
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
3889
+ set({ failedProviders: normalized });
3890
+ },
3891
+ addFailedProvider: (baseUrl) => {
3892
+ const normalized = normalizeBaseUrl5(baseUrl);
3893
+ const current = get().failedProviders;
3894
+ if (!current.includes(normalized)) {
3895
+ const updated = [...current, normalized];
3896
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
3897
+ set({ failedProviders: updated });
3898
+ }
3899
+ },
3900
+ removeFailedProvider: (baseUrl) => {
3901
+ const normalized = normalizeBaseUrl5(baseUrl);
3902
+ const current = get().failedProviders;
3903
+ const updated = current.filter((url) => url !== normalized);
3904
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
3905
+ set({ failedProviders: updated });
3906
+ },
3907
+ setLastFailed: (value) => {
3908
+ const normalized = {};
3909
+ for (const [baseUrl, timestamp] of Object.entries(value)) {
3910
+ normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
3911
+ }
3912
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
3913
+ set({ lastFailed: normalized });
3914
+ },
3915
+ setLastFailedTimestamp: (baseUrl, timestamp) => {
3916
+ const normalized = normalizeBaseUrl5(baseUrl);
3917
+ const current = get().lastFailed;
3918
+ const updated = { ...current, [normalized]: timestamp };
3919
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
3920
+ set({ lastFailed: updated });
3921
+ },
3922
+ setProvidersOnCooldown: (value) => {
3923
+ const normalized = value.map((entry) => ({
3924
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3925
+ timestamp: entry.timestamp
3926
+ }));
3927
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
3928
+ set({ providersOnCooldown: normalized });
3929
+ },
3930
+ addProviderOnCooldown: (baseUrl, timestamp) => {
3931
+ const normalized = normalizeBaseUrl5(baseUrl);
3932
+ const current = get().providersOnCooldown;
3933
+ if (!current.some((entry) => entry.baseUrl === normalized)) {
3934
+ const updated = [...current, { baseUrl: normalized, timestamp }];
3935
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
3936
+ set({ providersOnCooldown: updated });
3937
+ }
3938
+ },
3939
+ removeProviderFromCooldown: (baseUrl) => {
3940
+ const normalized = normalizeBaseUrl5(baseUrl);
3941
+ const current = get().providersOnCooldown;
3942
+ const updated = current.filter((entry) => entry.baseUrl !== normalized);
3943
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
3944
+ set({ providersOnCooldown: updated });
3945
+ },
3946
+ clearProvidersOnCooldown: () => {
3947
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, []);
3948
+ set({ providersOnCooldown: [] });
3645
3949
  }
3646
3950
  }));
3647
3951
  var hydrateStoreFromDriver = async (store, driver) => {
@@ -3654,13 +3958,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
3654
3958
  rawMints,
3655
3959
  rawInfo,
3656
3960
  rawLastModelsUpdate,
3657
- rawCachedTokens,
3658
3961
  rawApiKeys,
3659
3962
  rawChildKeys,
3963
+ rawXcashuTokens,
3660
3964
  rawRoutstr21Models,
3661
3965
  rawLastRoutstr21ModelsUpdate,
3662
3966
  rawCachedReceiveTokens,
3663
- rawClientIds
3967
+ rawClientIds,
3968
+ rawFailedProviders,
3969
+ rawLastFailed,
3970
+ rawProvidersOnCooldown
3664
3971
  ] = await Promise.all([
3665
3972
  driver.getItem(
3666
3973
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3682,65 +3989,73 @@ var hydrateStoreFromDriver = async (store, driver) => {
3682
3989
  SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
3683
3990
  {}
3684
3991
  ),
3685
- driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
3686
3992
  driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
3687
3993
  driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
3994
+ driver.getItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, {}),
3688
3995
  driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
3689
3996
  driver.getItem(
3690
3997
  SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
3691
3998
  null
3692
3999
  ),
3693
4000
  driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
3694
- driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
4001
+ driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, []),
4002
+ driver.getItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, []),
4003
+ driver.getItem(SDK_STORAGE_KEYS.LAST_FAILED, {}),
4004
+ driver.getItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, [])
3695
4005
  ]);
3696
4006
  const modelsFromAllProviders = Object.fromEntries(
3697
4007
  Object.entries(rawModels).map(([baseUrl, models]) => [
3698
- normalizeBaseUrl4(baseUrl),
4008
+ normalizeBaseUrl5(baseUrl),
3699
4009
  models
3700
4010
  ])
3701
4011
  );
3702
- const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl4(url));
4012
+ const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl5(url));
3703
4013
  const disabledProviders = rawDisabledProviders.map(
3704
- (url) => normalizeBaseUrl4(url)
4014
+ (url) => normalizeBaseUrl5(url)
3705
4015
  );
3706
4016
  const mintsFromAllProviders = Object.fromEntries(
3707
4017
  Object.entries(rawMints).map(([baseUrl, mints]) => [
3708
- normalizeBaseUrl4(baseUrl),
4018
+ normalizeBaseUrl5(baseUrl),
3709
4019
  mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
3710
4020
  ])
3711
4021
  );
3712
4022
  const infoFromAllProviders = Object.fromEntries(
3713
4023
  Object.entries(rawInfo).map(([baseUrl, info]) => [
3714
- normalizeBaseUrl4(baseUrl),
4024
+ normalizeBaseUrl5(baseUrl),
3715
4025
  info
3716
4026
  ])
3717
4027
  );
3718
4028
  const lastModelsUpdate = Object.fromEntries(
3719
4029
  Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
3720
- normalizeBaseUrl4(baseUrl),
4030
+ normalizeBaseUrl5(baseUrl),
3721
4031
  timestamp
3722
4032
  ])
3723
4033
  );
3724
- const cachedTokens = rawCachedTokens.map((entry) => ({
3725
- ...entry,
3726
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
3727
- balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
3728
- lastUsed: entry.lastUsed ?? null
3729
- }));
3730
4034
  const apiKeys = rawApiKeys.map((entry) => ({
3731
4035
  ...entry,
3732
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
4036
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3733
4037
  balance: entry.balance ?? 0,
3734
4038
  lastUsed: entry.lastUsed ?? null
3735
4039
  }));
3736
4040
  const childKeys = rawChildKeys.map((entry) => ({
3737
- parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
4041
+ parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
3738
4042
  childKey: entry.childKey,
3739
4043
  balance: entry.balance ?? 0,
3740
4044
  balanceLimit: entry.balanceLimit,
3741
4045
  validityDate: entry.validityDate,
3742
4046
  createdAt: entry.createdAt ?? Date.now()
3743
4047
  }));
4048
+ const xcashuTokens = Object.fromEntries(
4049
+ Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
4050
+ normalizeBaseUrl5(baseUrl),
4051
+ tokens.map((entry) => ({
4052
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
4053
+ token: entry.token,
4054
+ createdAt: entry.createdAt ?? Date.now(),
4055
+ tryCount: entry.tryCount ?? 0
4056
+ }))
4057
+ ])
4058
+ );
3744
4059
  const routstr21Models = rawRoutstr21Models;
3745
4060
  const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
3746
4061
  const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
@@ -3754,6 +4069,17 @@ var hydrateStoreFromDriver = async (store, driver) => {
3754
4069
  createdAt: entry.createdAt ?? Date.now(),
3755
4070
  lastUsed: entry.lastUsed ?? null
3756
4071
  }));
4072
+ const failedProviders = rawFailedProviders.map((url) => normalizeBaseUrl5(url));
4073
+ const lastFailed = Object.fromEntries(
4074
+ Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
4075
+ normalizeBaseUrl5(baseUrl),
4076
+ timestamp
4077
+ ])
4078
+ );
4079
+ const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
4080
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
4081
+ timestamp: entry.timestamp
4082
+ }));
3757
4083
  store.setState({
3758
4084
  modelsFromAllProviders,
3759
4085
  lastUsedModel,
@@ -3763,13 +4089,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
3763
4089
  mintsFromAllProviders,
3764
4090
  infoFromAllProviders,
3765
4091
  lastModelsUpdate,
3766
- cachedTokens,
3767
4092
  apiKeys,
3768
4093
  childKeys,
4094
+ xcashuTokens,
3769
4095
  routstr21Models,
3770
4096
  lastRoutstr21ModelsUpdate,
3771
4097
  cachedReceiveTokens,
3772
- clientIds
4098
+ clientIds,
4099
+ failedProviders,
4100
+ lastFailed,
4101
+ providersOnCooldown
3773
4102
  });
3774
4103
  };
3775
4104
  var createSdkStore = ({
@@ -3789,12 +4118,12 @@ var createDiscoveryAdapterFromStore = (store) => ({
3789
4118
  getCachedProviderInfo: () => store.getState().infoFromAllProviders,
3790
4119
  setCachedProviderInfo: (info) => store.getState().setInfoFromAllProviders(info),
3791
4120
  getProviderLastUpdate: (baseUrl) => {
3792
- const normalized = normalizeBaseUrl4(baseUrl);
4121
+ const normalized = normalizeBaseUrl5(baseUrl);
3793
4122
  const timestamps = store.getState().lastModelsUpdate;
3794
4123
  return timestamps[normalized] || null;
3795
4124
  },
3796
4125
  setProviderLastUpdate: (baseUrl, timestamp) => {
3797
- const normalized = normalizeBaseUrl4(baseUrl);
4126
+ const normalized = normalizeBaseUrl5(baseUrl);
3798
4127
  const timestamps = { ...store.getState().lastModelsUpdate };
3799
4128
  timestamps[normalized] = timestamp;
3800
4129
  store.getState().setLastModelsUpdate(timestamps);
@@ -3812,59 +4141,6 @@ var createDiscoveryAdapterFromStore = (store) => ({
3812
4141
  setRoutstr21ModelsLastUpdate: (timestamp) => store.getState().setRoutstr21ModelsLastUpdate(timestamp)
3813
4142
  });
3814
4143
  var createStorageAdapterFromStore = (store) => ({
3815
- getToken: (baseUrl) => {
3816
- const normalized = normalizeBaseUrl4(baseUrl);
3817
- const entry = store.getState().cachedTokens.find((token) => token.baseUrl === normalized);
3818
- if (!entry) return null;
3819
- const next = store.getState().cachedTokens.map(
3820
- (token) => token.baseUrl === normalized ? { ...token, lastUsed: Date.now() } : token
3821
- );
3822
- store.getState().setCachedTokens(next);
3823
- return entry.token;
3824
- },
3825
- setToken: (baseUrl, token) => {
3826
- const normalized = normalizeBaseUrl4(baseUrl);
3827
- const tokens = store.getState().cachedTokens;
3828
- const balance = getCashuTokenBalance(token);
3829
- const existingIndex = tokens.findIndex(
3830
- (entry) => entry.baseUrl === normalized
3831
- );
3832
- if (existingIndex !== -1) {
3833
- throw new Error(`Token already exists for baseUrl: ${normalized}`);
3834
- }
3835
- const next = [...tokens];
3836
- next.push({
3837
- baseUrl: normalized,
3838
- token,
3839
- balance,
3840
- lastUsed: Date.now()
3841
- });
3842
- store.getState().setCachedTokens(next);
3843
- },
3844
- removeToken: (baseUrl) => {
3845
- const normalized = normalizeBaseUrl4(baseUrl);
3846
- const next = store.getState().cachedTokens.filter((entry) => entry.baseUrl !== normalized);
3847
- store.getState().setCachedTokens(next);
3848
- },
3849
- updateTokenBalance: (baseUrl, balance) => {
3850
- const normalized = normalizeBaseUrl4(baseUrl);
3851
- const tokens = store.getState().cachedTokens;
3852
- const next = tokens.map(
3853
- (entry) => entry.baseUrl === normalized ? { ...entry, balance } : entry
3854
- );
3855
- store.getState().setCachedTokens(next);
3856
- },
3857
- getCachedTokenDistribution: () => {
3858
- const cachedTokens = store.getState().cachedTokens;
3859
- const distributionMap = {};
3860
- for (const entry of cachedTokens) {
3861
- const sum = entry.balance || 0;
3862
- if (sum > 0) {
3863
- distributionMap[entry.baseUrl] = (distributionMap[entry.baseUrl] || 0) + sum;
3864
- }
3865
- }
3866
- return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
3867
- },
3868
4144
  getApiKeyDistribution: () => {
3869
4145
  const apiKeys = store.getState().apiKeys;
3870
4146
  const distributionMap = {};
@@ -3877,28 +4153,24 @@ var createStorageAdapterFromStore = (store) => ({
3877
4153
  return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
3878
4154
  },
3879
4155
  saveProviderInfo: (baseUrl, info) => {
3880
- const normalized = normalizeBaseUrl4(baseUrl);
4156
+ const normalized = normalizeBaseUrl5(baseUrl);
3881
4157
  const next = { ...store.getState().infoFromAllProviders };
3882
4158
  next[normalized] = info;
3883
4159
  store.getState().setInfoFromAllProviders(next);
3884
4160
  },
3885
4161
  getProviderInfo: (baseUrl) => {
3886
- const normalized = normalizeBaseUrl4(baseUrl);
4162
+ const normalized = normalizeBaseUrl5(baseUrl);
3887
4163
  return store.getState().infoFromAllProviders[normalized] || null;
3888
4164
  },
3889
4165
  // ========== API Keys (for apikeys mode) ==========
3890
4166
  getApiKey: (baseUrl) => {
3891
- const normalized = normalizeBaseUrl4(baseUrl);
4167
+ const normalized = normalizeBaseUrl5(baseUrl);
3892
4168
  const entry = store.getState().apiKeys.find((key) => key.baseUrl === normalized);
3893
4169
  if (!entry) return null;
3894
- const next = store.getState().apiKeys.map(
3895
- (key) => key.baseUrl === normalized ? { ...key, lastUsed: Date.now() } : key
3896
- );
3897
- store.getState().setApiKeys(next);
3898
4170
  return entry;
3899
4171
  },
3900
4172
  setApiKey: (baseUrl, key) => {
3901
- const normalized = normalizeBaseUrl4(baseUrl);
4173
+ const normalized = normalizeBaseUrl5(baseUrl);
3902
4174
  const keys = store.getState().apiKeys;
3903
4175
  const existingIndex = keys.findIndex(
3904
4176
  (entry) => entry.baseUrl === normalized
@@ -3916,15 +4188,15 @@ var createStorageAdapterFromStore = (store) => ({
3916
4188
  store.getState().setApiKeys(next);
3917
4189
  },
3918
4190
  updateApiKeyBalance: (baseUrl, balance) => {
3919
- const normalized = normalizeBaseUrl4(baseUrl);
4191
+ const normalized = normalizeBaseUrl5(baseUrl);
3920
4192
  const keys = store.getState().apiKeys;
3921
4193
  const next = keys.map(
3922
- (entry) => entry.baseUrl === normalized ? { ...entry, balance } : entry
4194
+ (entry) => entry.baseUrl === normalized ? { ...entry, balance, lastUsed: Date.now() } : entry
3923
4195
  );
3924
4196
  store.getState().setApiKeys(next);
3925
4197
  },
3926
4198
  removeApiKey: (baseUrl) => {
3927
- const normalized = normalizeBaseUrl4(baseUrl);
4199
+ const normalized = normalizeBaseUrl5(baseUrl);
3928
4200
  const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
3929
4201
  store.getState().setApiKeys(next);
3930
4202
  },
@@ -3938,7 +4210,7 @@ var createStorageAdapterFromStore = (store) => ({
3938
4210
  },
3939
4211
  // ========== Child Keys ==========
3940
4212
  getChildKey: (parentBaseUrl) => {
3941
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4213
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3942
4214
  const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
3943
4215
  if (!entry) return null;
3944
4216
  return {
@@ -3951,7 +4223,7 @@ var createStorageAdapterFromStore = (store) => ({
3951
4223
  };
3952
4224
  },
3953
4225
  setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
3954
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4226
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3955
4227
  const keys = store.getState().childKeys;
3956
4228
  const existingIndex = keys.findIndex(
3957
4229
  (entry) => entry.parentBaseUrl === normalized
@@ -3982,7 +4254,7 @@ var createStorageAdapterFromStore = (store) => ({
3982
4254
  }
3983
4255
  },
3984
4256
  updateChildKeyBalance: (parentBaseUrl, balance) => {
3985
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4257
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3986
4258
  const keys = store.getState().childKeys;
3987
4259
  const next = keys.map(
3988
4260
  (entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
@@ -3990,7 +4262,7 @@ var createStorageAdapterFromStore = (store) => ({
3990
4262
  store.getState().setChildKeys(next);
3991
4263
  },
3992
4264
  removeChildKey: (parentBaseUrl) => {
3993
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4265
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3994
4266
  const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
3995
4267
  store.getState().setChildKeys(next);
3996
4268
  },
@@ -4009,20 +4281,60 @@ var createStorageAdapterFromStore = (store) => ({
4009
4281
  },
4010
4282
  setCachedReceiveTokens: (tokens) => {
4011
4283
  store.getState().setCachedReceiveTokens(tokens);
4284
+ },
4285
+ // ========== XCashu Tokens (multiple tokens per baseUrl) ==========
4286
+ getXcashuTokens: () => {
4287
+ return store.getState().xcashuTokens;
4288
+ },
4289
+ getXcashuTokensForBaseUrl: (baseUrl) => {
4290
+ const normalized = normalizeBaseUrl5(baseUrl);
4291
+ return store.getState().xcashuTokens[normalized] || [];
4292
+ },
4293
+ addXcashuToken: (baseUrl, token) => {
4294
+ const normalized = normalizeBaseUrl5(baseUrl);
4295
+ const tokens = store.getState().xcashuTokens;
4296
+ const existing = tokens[normalized] || [];
4297
+ const next = { ...tokens };
4298
+ next[normalized] = [
4299
+ ...existing,
4300
+ { baseUrl: normalized, token, createdAt: Date.now(), tryCount: 0 }
4301
+ ];
4302
+ store.getState().setXcashuTokens(next);
4303
+ },
4304
+ removeXcashuToken: (baseUrl, token) => {
4305
+ const normalized = normalizeBaseUrl5(baseUrl);
4306
+ const tokens = store.getState().xcashuTokens;
4307
+ const existing = tokens[normalized] || [];
4308
+ const next = { ...tokens };
4309
+ next[normalized] = existing.filter((entry) => entry.token !== token);
4310
+ if (next[normalized].length === 0) {
4311
+ delete next[normalized];
4312
+ }
4313
+ store.getState().setXcashuTokens(next);
4314
+ },
4315
+ clearXcashuTokensForBaseUrl: (baseUrl) => {
4316
+ const normalized = normalizeBaseUrl5(baseUrl);
4317
+ const tokens = store.getState().xcashuTokens;
4318
+ const next = { ...tokens };
4319
+ delete next[normalized];
4320
+ store.getState().setXcashuTokens(next);
4321
+ },
4322
+ updateXcashuTokenTryCount: (token, tryCount) => {
4323
+ store.getState().updateXcashuTokenTryCount(token, tryCount);
4012
4324
  }
4013
4325
  });
4014
4326
  var createProviderRegistryFromStore = (store) => ({
4015
4327
  getModelsForProvider: (baseUrl) => {
4016
- const normalized = normalizeBaseUrl4(baseUrl);
4328
+ const normalized = normalizeBaseUrl5(baseUrl);
4017
4329
  return store.getState().modelsFromAllProviders[normalized] || [];
4018
4330
  },
4019
4331
  getDisabledProviders: () => store.getState().disabledProviders,
4020
4332
  getProviderMints: (baseUrl) => {
4021
- const normalized = normalizeBaseUrl4(baseUrl);
4333
+ const normalized = normalizeBaseUrl5(baseUrl);
4022
4334
  return store.getState().mintsFromAllProviders[normalized] || [];
4023
4335
  },
4024
4336
  getProviderInfo: async (baseUrl) => {
4025
- const normalized = normalizeBaseUrl4(baseUrl);
4337
+ const normalized = normalizeBaseUrl5(baseUrl);
4026
4338
  const cached = store.getState().infoFromAllProviders[normalized];
4027
4339
  if (cached) return cached;
4028
4340
  try {
@@ -4097,7 +4409,7 @@ var getDefaultUsageTrackingDriver = () => {
4097
4409
  return defaultUsageTrackingDriver;
4098
4410
  }
4099
4411
  if (isBun3()) {
4100
- defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
4412
+ defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
4101
4413
  return defaultUsageTrackingDriver;
4102
4414
  }
4103
4415
  if (isNode()) {
@@ -4109,21 +4421,28 @@ var getDefaultUsageTrackingDriver = () => {
4109
4421
  defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
4110
4422
  return defaultUsageTrackingDriver;
4111
4423
  };
4424
+ var setDefaultUsageTrackingDriver = (driver) => {
4425
+ defaultUsageTrackingDriver = driver;
4426
+ };
4112
4427
  var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
4113
4428
  var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
4114
4429
  var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
4115
4430
  function createSSEParserTransform(onUsage, onResponseId) {
4116
4431
  let buffer = "";
4432
+ let usageCaptured = false;
4433
+ let responseIdCaptured = false;
4117
4434
  const maybeCaptureUsageFromJson = (jsonText) => {
4118
4435
  try {
4119
4436
  const data = JSON.parse(jsonText);
4120
4437
  const responseId = data.id;
4121
4438
  if (typeof responseId === "string" && responseId.trim().length > 0) {
4122
4439
  onResponseId?.(responseId.trim());
4440
+ responseIdCaptured = true;
4123
4441
  }
4124
4442
  const usage = extractUsageFromSSEJson(data);
4125
4443
  if (usage) {
4126
4444
  onUsage(usage);
4445
+ usageCaptured = true;
4127
4446
  }
4128
4447
  } catch {
4129
4448
  }
@@ -4179,7 +4498,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
4179
4498
  }
4180
4499
  var TOPUP_MARGIN = 1.2;
4181
4500
  var RoutstrClient = class {
4182
- constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
4501
+ constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
4183
4502
  this.walletAdapter = walletAdapter;
4184
4503
  this.storageAdapter = storageAdapter;
4185
4504
  this.providerRegistry = providerRegistry;
@@ -4195,15 +4514,11 @@ var RoutstrClient = class {
4195
4514
  this.balanceManager
4196
4515
  );
4197
4516
  this.streamProcessor = new StreamProcessor();
4198
- this.providerManager = new ProviderManager(providerRegistry);
4199
4517
  this.alertLevel = alertLevel;
4200
- if (mode === "lazyrefund") {
4201
- this.mode = "apikeys";
4202
- } else if (mode === "apikeys") {
4203
- this.mode = "lazyrefund";
4204
- } else {
4205
- this.mode = mode;
4206
- }
4518
+ this.mode = mode;
4519
+ this.usageTrackingDriver = options.usageTrackingDriver;
4520
+ this.sdkStore = options.sdkStore;
4521
+ this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore);
4207
4522
  }
4208
4523
  cashuSpender;
4209
4524
  balanceManager;
@@ -4212,6 +4527,8 @@ var RoutstrClient = class {
4212
4527
  alertLevel;
4213
4528
  mode;
4214
4529
  debugLevel = "WARN";
4530
+ usageTrackingDriver;
4531
+ sdkStore;
4215
4532
  /**
4216
4533
  * Get the current client mode
4217
4534
  */
@@ -4281,11 +4598,13 @@ var RoutstrClient = class {
4281
4598
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4282
4599
  token: prepared.tokenUsed,
4283
4600
  baseUrl: prepared.baseUrlUsed,
4601
+ mintUrl: params.mintUrl,
4284
4602
  initialTokenBalance: prepared.tokenBalanceInSats,
4285
4603
  response: prepared.response,
4286
4604
  modelId: prepared.modelId,
4287
4605
  usage: prepared.capturedUsage,
4288
- requestId: prepared.capturedResponseId
4606
+ requestId: prepared.capturedResponseId,
4607
+ clientApiKey: prepared.clientApiKey
4289
4608
  });
4290
4609
  prepared.response.satsSpent = satsSpent;
4291
4610
  prepared.response.usage = prepared.capturedUsage;
@@ -4304,11 +4623,13 @@ var RoutstrClient = class {
4304
4623
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4305
4624
  token: prepared.tokenUsed,
4306
4625
  baseUrl: prepared.baseUrlUsed,
4626
+ mintUrl: params.mintUrl,
4307
4627
  initialTokenBalance: prepared.tokenBalanceInSats,
4308
4628
  response: prepared.response,
4309
4629
  modelId: prepared.modelId,
4310
4630
  usage: prepared.capturedUsage,
4311
- requestId: prepared.capturedResponseId
4631
+ requestId: prepared.capturedResponseId,
4632
+ clientApiKey: prepared.clientApiKey
4312
4633
  });
4313
4634
  prepared.response.satsSpent = satsSpent;
4314
4635
  res.end();
@@ -4324,11 +4645,13 @@ var RoutstrClient = class {
4324
4645
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4325
4646
  token: prepared.tokenUsed,
4326
4647
  baseUrl: prepared.baseUrlUsed,
4648
+ mintUrl: params.mintUrl,
4327
4649
  initialTokenBalance: prepared.tokenBalanceInSats,
4328
4650
  response: prepared.response,
4329
4651
  modelId: prepared.modelId,
4330
4652
  usage: prepared.capturedUsage,
4331
- requestId: prepared.capturedResponseId
4653
+ requestId: prepared.capturedResponseId,
4654
+ clientApiKey: prepared.clientApiKey
4332
4655
  });
4333
4656
  prepared.response.satsSpent = satsSpent;
4334
4657
  prepared.response.usage = prepared.capturedUsage;
@@ -4358,8 +4681,10 @@ var RoutstrClient = class {
4358
4681
  headers = {},
4359
4682
  baseUrl,
4360
4683
  mintUrl,
4361
- modelId
4684
+ modelId,
4685
+ clientApiKey: providedClientApiKey
4362
4686
  } = params;
4687
+ const clientApiKey = providedClientApiKey ?? this._extractClientApiKey(headers);
4363
4688
  await this._checkBalance();
4364
4689
  let requiredSats = 1;
4365
4690
  let selectedModel;
@@ -4381,7 +4706,6 @@ var RoutstrClient = class {
4381
4706
  amount: requiredSats,
4382
4707
  baseUrl
4383
4708
  });
4384
- this._log("DEBUG", token, baseUrl);
4385
4709
  let requestBody = body;
4386
4710
  if (body && typeof body === "object") {
4387
4711
  const bodyObj = body;
@@ -4389,7 +4713,7 @@ var RoutstrClient = class {
4389
4713
  requestBody = { ...bodyObj, stream: false };
4390
4714
  }
4391
4715
  }
4392
- const baseHeaders = this._buildBaseHeaders(headers);
4716
+ const baseHeaders = this._buildBaseHeaders();
4393
4717
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4394
4718
  const response = await this._makeRequest({
4395
4719
  path,
@@ -4441,9 +4765,21 @@ var RoutstrClient = class {
4441
4765
  tokenBalanceInSats,
4442
4766
  modelId,
4443
4767
  capturedUsage,
4444
- capturedResponseId
4768
+ capturedResponseId,
4769
+ clientApiKey
4445
4770
  };
4446
4771
  }
4772
+ /**
4773
+ * Extract clientApiKey from Authorization Bearer token if present
4774
+ */
4775
+ _extractClientApiKey(headers) {
4776
+ const authHeader = headers["Authorization"] || headers["authorization"];
4777
+ if (authHeader?.startsWith("Bearer ")) {
4778
+ const extractedKey = authHeader.slice(7);
4779
+ return extractedKey;
4780
+ }
4781
+ return void 0;
4782
+ }
4447
4783
  /**
4448
4784
  * Fetch AI response with streaming
4449
4785
  */
@@ -4547,6 +4883,7 @@ var RoutstrClient = class {
4547
4883
  let satsSpent = await this._handlePostResponseBalanceUpdate({
4548
4884
  token,
4549
4885
  baseUrl: baseUrlUsed,
4886
+ mintUrl,
4550
4887
  initialTokenBalance: tokenBalanceInSats,
4551
4888
  fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
4552
4889
  response,
@@ -4585,7 +4922,6 @@ var RoutstrClient = class {
4585
4922
  try {
4586
4923
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
4587
4924
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
4588
- this._log("DEBUG", "HEADERS,", headers);
4589
4925
  const response = await fetch(url, {
4590
4926
  method,
4591
4927
  headers,
@@ -4654,8 +4990,6 @@ var RoutstrClient = class {
4654
4990
  `[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
4655
4991
  );
4656
4992
  tryNextProvider = true;
4657
- if (this.mode === "lazyrefund")
4658
- this.storageAdapter.removeToken(baseUrl);
4659
4993
  } else {
4660
4994
  this._log(
4661
4995
  "DEBUG",
@@ -4703,24 +5037,21 @@ var RoutstrClient = class {
4703
5037
  );
4704
5038
  }
4705
5039
  }
4706
- if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
5040
+ if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
4707
5041
  this.storageAdapter.getApiKey(baseUrl);
4708
5042
  let topupAmount = params.requiredSats;
4709
5043
  try {
4710
- let currentBalance = 0;
4711
- if (this.mode === "apikeys") {
4712
- const currentBalanceInfo = await this.balanceManager.getTokenBalance(
4713
- params.token,
4714
- baseUrl
4715
- );
4716
- currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
4717
- } else if (this.mode === "lazyrefund") {
4718
- const distribution = this.storageAdapter.getCachedTokenDistribution();
4719
- const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
4720
- currentBalance = tokenEntry?.amount ?? 0;
4721
- }
5044
+ const currentBalanceInfo = await this.balanceManager.getTokenBalance(
5045
+ params.token,
5046
+ baseUrl
5047
+ );
5048
+ const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
4722
5049
  const shortfall = Math.max(0, params.requiredSats - currentBalance);
4723
5050
  topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
5051
+ this._log(
5052
+ "DEBUG",
5053
+ `The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
5054
+ );
4724
5055
  } catch (e) {
4725
5056
  this._log(
4726
5057
  "WARN",
@@ -4850,61 +5181,49 @@ var RoutstrClient = class {
4850
5181
  tryNextProvider = true;
4851
5182
  }
4852
5183
  }
5184
+ if (status === 401 && this.mode === "apikeys") {
5185
+ this._log(
5186
+ "DEBUG",
5187
+ `[RoutstrClient] _handleErrorResponse: Checking balance for ${baseUrl}, key preview=${token}`
5188
+ );
5189
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
5190
+ token,
5191
+ baseUrl
5192
+ );
5193
+ if (latestBalanceInfo.isInvalidApiKey) {
5194
+ this.storageAdapter.removeApiKey(baseUrl);
5195
+ tryNextProvider = true;
5196
+ }
5197
+ }
4853
5198
  if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
4854
5199
  this._log(
4855
5200
  "DEBUG",
4856
5201
  `[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
4857
5202
  );
4858
- if (this.mode === "lazyrefund") {
4859
- try {
4860
- const refundResult = await this.balanceManager.refund({
4861
- mintUrl,
4862
- baseUrl,
4863
- token: params.token
4864
- });
4865
- this._log(
4866
- "DEBUG",
4867
- `[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
4868
- );
4869
- if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
4870
- else
4871
- throw new ProviderError(
4872
- baseUrl,
4873
- status,
4874
- "refund failed",
4875
- requestId
4876
- );
4877
- } catch (error) {
4878
- throw new ProviderError(
4879
- baseUrl,
4880
- status,
4881
- "Failed to refund token",
4882
- requestId
4883
- );
4884
- }
4885
- } else if (this.mode === "apikeys") {
5203
+ if (this.mode === "apikeys") {
4886
5204
  this._log(
4887
5205
  "DEBUG",
4888
5206
  `[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
4889
5207
  );
4890
- const initialBalance = await this.balanceManager.getTokenBalance(
5208
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
4891
5209
  token,
4892
5210
  baseUrl
4893
5211
  );
4894
5212
  this._log(
4895
5213
  "DEBUG",
4896
- `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${initialBalance.amount}`
5214
+ `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
4897
5215
  );
4898
5216
  const refundResult = await this.balanceManager.refundApiKey({
4899
5217
  mintUrl,
4900
5218
  baseUrl,
4901
- apiKey: token
5219
+ apiKey: token,
5220
+ forceRefund: true
4902
5221
  });
4903
5222
  this._log(
4904
5223
  "DEBUG",
4905
5224
  `[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
4906
5225
  );
4907
- if (!refundResult.success && initialBalance.amount > 0) {
5226
+ if (!refundResult.success && latestBalanceInfo.amount > 0) {
4908
5227
  throw new ProviderError(
4909
5228
  baseUrl,
4910
5229
  status,
@@ -4980,12 +5299,14 @@ var RoutstrClient = class {
4980
5299
  const {
4981
5300
  token,
4982
5301
  baseUrl,
5302
+ mintUrl,
4983
5303
  initialTokenBalance,
4984
5304
  fallbackSatsSpent,
4985
5305
  response,
4986
5306
  modelId,
4987
5307
  usage,
4988
- requestId
5308
+ requestId,
5309
+ clientApiKey
4989
5310
  } = params;
4990
5311
  let satsSpent = initialTokenBalance;
4991
5312
  if (this.mode === "xcashu" && response) {
@@ -4993,19 +5314,14 @@ var RoutstrClient = class {
4993
5314
  if (refundToken) {
4994
5315
  try {
4995
5316
  const receiveResult = await this.cashuSpender.receiveToken(refundToken);
4996
- satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
5317
+ if (receiveResult.success) {
5318
+ this.storageAdapter.removeXcashuToken(baseUrl, token);
5319
+ satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
5320
+ }
4997
5321
  } catch (error) {
4998
5322
  this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
4999
5323
  }
5000
5324
  }
5001
- } else if (this.mode === "lazyrefund") {
5002
- const latestBalanceInfo = await this.balanceManager.getTokenBalance(
5003
- token,
5004
- baseUrl
5005
- );
5006
- const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
5007
- this.storageAdapter.updateTokenBalance(baseUrl, latestTokenBalance);
5008
- satsSpent = initialTokenBalance - latestTokenBalance;
5009
5325
  } else if (this.mode === "apikeys") {
5010
5326
  try {
5011
5327
  const latestBalanceInfo = await this.balanceManager.getTokenBalance(
@@ -5040,8 +5356,17 @@ var RoutstrClient = class {
5040
5356
  modelId,
5041
5357
  satsSpent,
5042
5358
  usage,
5043
- requestId
5359
+ requestId,
5360
+ clientApiKey
5044
5361
  });
5362
+ (async () => {
5363
+ try {
5364
+ const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
5365
+ this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
5366
+ } catch (error) {
5367
+ this._log("ERROR", "Failed to refund providers:", error);
5368
+ }
5369
+ })();
5045
5370
  return satsSpent;
5046
5371
  }
5047
5372
  async _trackResponseUsage(params) {
@@ -5052,7 +5377,8 @@ var RoutstrClient = class {
5052
5377
  modelId,
5053
5378
  satsSpent,
5054
5379
  usage: providedUsage,
5055
- requestId: providedRequestId
5380
+ requestId: providedRequestId,
5381
+ clientApiKey
5056
5382
  } = params;
5057
5383
  if (!response || !modelId) {
5058
5384
  return;
@@ -5079,13 +5405,14 @@ var RoutstrClient = class {
5079
5405
  return;
5080
5406
  }
5081
5407
  const finalRequestId = requestId || "unknown";
5082
- const store = await getDefaultSdkStore();
5408
+ const store = this.sdkStore ?? await getDefaultSdkStore();
5083
5409
  const state = store.getState();
5410
+ const matchKey = clientApiKey ?? token;
5084
5411
  const matchingClient = state.clientIds.find(
5085
- (client) => client.apiKey === token
5412
+ (client) => client.apiKey === matchKey
5086
5413
  );
5087
5414
  const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
5088
- const usageTracking = getDefaultUsageTrackingDriver();
5415
+ const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
5089
5416
  const entry = {
5090
5417
  id: entryId,
5091
5418
  timestamp: Date.now(),
@@ -5160,11 +5487,11 @@ var RoutstrClient = class {
5160
5487
  return estimatedCosts;
5161
5488
  }
5162
5489
  /**
5163
- * Get pending cashu token amount
5490
+ * Get pending API key amount
5164
5491
  */
5165
5492
  _getPendingCashuTokenAmount() {
5166
- const distribution = this.storageAdapter.getCachedTokenDistribution();
5167
- return distribution.reduce((total, item) => total + item.amount, 0);
5493
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
5494
+ return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
5168
5495
  }
5169
5496
  /**
5170
5497
  * Handle errors and notify callbacks
@@ -5311,8 +5638,8 @@ var RoutstrClient = class {
5311
5638
  const spendResult = await this.cashuSpender.spend({
5312
5639
  mintUrl,
5313
5640
  amount,
5314
- baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
5315
- reuseToken: this.mode === "lazyrefund"
5641
+ baseUrl: "",
5642
+ reuseToken: false
5316
5643
  });
5317
5644
  if (!spendResult.token) {
5318
5645
  this._log(
@@ -5325,6 +5652,7 @@ var RoutstrClient = class {
5325
5652
  "DEBUG",
5326
5653
  `[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
5327
5654
  );
5655
+ this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
5328
5656
  }
5329
5657
  return {
5330
5658
  token: spendResult.token,
@@ -5362,6 +5690,7 @@ async function resolveRouteRequestContext(options) {
5362
5690
  modelId,
5363
5691
  requestBody,
5364
5692
  path = "/v1/chat/completions",
5693
+ headers = {},
5365
5694
  forcedProvider,
5366
5695
  walletAdapter,
5367
5696
  storageAdapter,
@@ -5372,7 +5701,10 @@ async function resolveRouteRequestContext(options) {
5372
5701
  forceRefresh = false,
5373
5702
  modelManager: providedModelManager,
5374
5703
  debugLevel,
5375
- mode = "apikeys"
5704
+ mode = "apikeys",
5705
+ usageTrackingDriver,
5706
+ sdkStore,
5707
+ providerManager: providedProviderManager
5376
5708
  } = options;
5377
5709
  let modelManager;
5378
5710
  let providers;
@@ -5392,7 +5724,7 @@ async function resolveRouteRequestContext(options) {
5392
5724
  }
5393
5725
  await modelManager.fetchModels(providers, forceRefresh);
5394
5726
  }
5395
- const providerManager = new ProviderManager(providerRegistry);
5727
+ const providerManager = providedProviderManager ?? new ProviderManager(providerRegistry, sdkStore);
5396
5728
  let baseUrl;
5397
5729
  let selectedModel;
5398
5730
  if (forcedProvider) {
@@ -5436,7 +5768,8 @@ async function resolveRouteRequestContext(options) {
5436
5768
  storageAdapter,
5437
5769
  providerRegistry,
5438
5770
  "min",
5439
- mode
5771
+ mode,
5772
+ { usageTrackingDriver, sdkStore, providerManager }
5440
5773
  );
5441
5774
  if (debugLevel) {
5442
5775
  client.setDebugLevel(debugLevel);
@@ -5456,17 +5789,19 @@ async function resolveRouteRequestContext(options) {
5456
5789
  baseUrl,
5457
5790
  mintUrl,
5458
5791
  path,
5792
+ headers,
5459
5793
  modelId,
5460
5794
  proxiedBody
5461
5795
  };
5462
5796
  }
5463
5797
  async function routeRequests(options) {
5464
- const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5798
+ const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5465
5799
  try {
5466
5800
  const response = await client.routeRequest({
5467
5801
  path,
5468
5802
  method: "POST",
5469
5803
  body: proxiedBody,
5804
+ headers,
5470
5805
  baseUrl,
5471
5806
  mintUrl,
5472
5807
  modelId
@@ -5484,12 +5819,13 @@ async function routeRequests(options) {
5484
5819
  }
5485
5820
  async function routeRequestsToNodeResponse(options) {
5486
5821
  const { res } = options;
5487
- const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5822
+ const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5488
5823
  try {
5489
5824
  await client.routeRequestToNodeResponse({
5490
5825
  path,
5491
5826
  method: "POST",
5492
5827
  body: proxiedBody,
5828
+ headers,
5493
5829
  baseUrl,
5494
5830
  mintUrl,
5495
5831
  modelId,
@@ -5519,6 +5855,6 @@ function extractStream(requestBody) {
5519
5855
  return typeof stream === "boolean" ? stream : void 0;
5520
5856
  }
5521
5857
 
5522
- export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createSqliteDriver, createSqliteUsageTrackingDriver, createStorageAdapterFromStore, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, isOnionUrl, isTorContext, localStorageDriver, normalizeProviderUrl, routeRequests, routeRequestsToNodeResponse };
5858
+ export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, createBunSqliteDriver, createBunSqliteUsageTrackingDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createIndexedDBUsageTrackingDriver, createMemoryDriver, createMemoryUsageTrackingDriver, createProviderRegistryFromStore, createSSEParserTransform, createSdkStore, createSqliteDriver, createSqliteUsageTrackingDriver, createStorageAdapterFromStore, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getDefaultUsageTrackingDriver, getProviderEndpoints, isOnionUrl, isTorContext, localStorageDriver, normalizeProviderUrl, routeRequests, routeRequestsToNodeResponse, setDefaultUsageTrackingDriver };
5523
5859
  //# sourceMappingURL=index.mjs.map
5524
5860
  //# sourceMappingURL=index.mjs.map