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