@routstr/sdk 0.2.5 → 0.2.6

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) {
@@ -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,108 @@ 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 and increment tryCounts on failure.
1087
+ * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
1088
+ * @param mintUrl - The mint URL for receiving tokens
1089
+ * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
1090
+ * @returns Results for each xcashu token refund attempt
1115
1091
  */
1116
- async refundProviders(baseUrls, mintUrl, refundApiKeys = false, forceRefund) {
1092
+ async refundXcashuTokens(mintUrl, excludeBaseUrls) {
1117
1093
  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);
1094
+ const xcashuTokens = this.storageAdapter.getXcashuTokens();
1095
+ const excludedUrls = new Set(excludeBaseUrls || []);
1096
+ for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
1097
+ if (excludedUrls.has(baseUrl)) continue;
1098
+ for (const xcashuToken of tokens) {
1099
+ try {
1100
+ const receiveResult = await this.receiveToken(xcashuToken.token);
1101
+ if (receiveResult.success) {
1102
+ this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
1103
+ results.push({
1104
+ baseUrl,
1105
+ token: xcashuToken.token,
1106
+ success: true
1107
+ });
1108
+ this._log(
1109
+ "DEBUG",
1110
+ `[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
1111
+ );
1112
+ } else {
1113
+ const currentTryCount = xcashuToken.tryCount ?? 0;
1114
+ const newTryCount = currentTryCount + 1;
1115
+ this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
1116
+ results.push({
1117
+ baseUrl,
1118
+ token: xcashuToken.token,
1119
+ success: false,
1120
+ error: receiveResult.message ?? "Refund failed"
1121
+ });
1122
+ this._log(
1123
+ "DEBUG",
1124
+ `[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
1125
+ );
1171
1126
  }
1127
+ } catch (error) {
1128
+ const currentTryCount = xcashuToken.tryCount ?? 0;
1129
+ const newTryCount = currentTryCount + 1;
1130
+ this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
1131
+ const errorMessage = error instanceof Error ? error.message : String(error);
1172
1132
  results.push({
1173
- baseUrl: apiKeyEntry.baseUrl,
1174
- success: refundResult.success
1133
+ baseUrl,
1134
+ token: xcashuToken.token,
1135
+ success: false,
1136
+ error: errorMessage
1175
1137
  });
1138
+ this._log(
1139
+ "ERROR",
1140
+ `[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
1141
+ );
1142
+ }
1143
+ }
1144
+ }
1145
+ return results;
1146
+ }
1147
+ /**
1148
+ * Refund specific providers without retrying spend
1149
+ */
1150
+ async refundProviders(mintUrl, forceRefund) {
1151
+ const results = [];
1152
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1153
+ for (const apiKeyEntry of apiKeyDistribution) {
1154
+ const apiKeyEntryFull = this.storageAdapter.getApiKey(
1155
+ apiKeyEntry.baseUrl
1156
+ );
1157
+ if (apiKeyEntryFull && this.balanceManager) {
1158
+ const refundResult = await this.balanceManager.refundApiKey({
1159
+ mintUrl,
1160
+ baseUrl: apiKeyEntry.baseUrl,
1161
+ apiKey: apiKeyEntryFull.key,
1162
+ forceRefund
1163
+ });
1164
+ if (refundResult.success) {
1165
+ this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
1176
1166
  } else {
1177
- results.push({
1178
- baseUrl: apiKeyEntry.baseUrl,
1179
- success: false
1180
- });
1167
+ this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
1181
1168
  }
1169
+ results.push({
1170
+ baseUrl: apiKeyEntry.baseUrl,
1171
+ success: refundResult.success
1172
+ });
1173
+ } else {
1174
+ results.push({
1175
+ baseUrl: apiKeyEntry.baseUrl,
1176
+ success: false
1177
+ });
1182
1178
  }
1183
1179
  }
1184
1180
  return results;
@@ -1263,13 +1259,8 @@ var BalanceManager = class {
1263
1259
  normalizedMintBalances[url] = balanceInSats;
1264
1260
  totalMintBalance += balanceInSats;
1265
1261
  }
1266
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1267
1262
  const providerBalances = {};
1268
1263
  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
1264
  const apiKeys = this.storageAdapter.getAllApiKeys();
1274
1265
  for (const apiKey of apiKeys) {
1275
1266
  if (!providerBalances[apiKey.baseUrl]) {
@@ -1284,57 +1275,6 @@ var BalanceManager = class {
1284
1275
  mintBalances: normalizedMintBalances
1285
1276
  };
1286
1277
  }
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
1278
  /**
1339
1279
  * Refund API key balance - convert remaining API key balance to cashu token
1340
1280
  * @param options - Refund options including forceRefund flag
@@ -1470,8 +1410,9 @@ var BalanceManager = class {
1470
1410
  if (!amount || amount <= 0) {
1471
1411
  return { success: false, message: "Invalid top up amount" };
1472
1412
  }
1473
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
1474
- if (!storedToken) {
1413
+ const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
1414
+ const apiKey = providedToken || apiKeyEntry?.key;
1415
+ if (!apiKey) {
1475
1416
  return { success: false, message: "No API key available for top up" };
1476
1417
  }
1477
1418
  let cashuToken = null;
@@ -1491,7 +1432,7 @@ var BalanceManager = class {
1491
1432
  cashuToken = tokenResult.token;
1492
1433
  const topUpResult = await this._postTopUp(
1493
1434
  baseUrl,
1494
- storedToken,
1435
+ apiKey,
1495
1436
  cashuToken
1496
1437
  );
1497
1438
  requestId = topUpResult.requestId;
@@ -1706,38 +1647,11 @@ var BalanceManager = class {
1706
1647
  return candidates;
1707
1648
  }
1708
1649
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
1709
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1710
1650
  const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1711
1651
  const forceRefund = retryCount >= 2;
1712
- const toRefund = pendingDistribution.filter(
1713
- (pending) => pending.baseUrl !== baseUrl
1714
- );
1715
1652
  const apiKeysToRefund = apiKeyDistribution.filter(
1716
1653
  (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1717
1654
  );
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
1655
  const apiKeyRefundResults = await Promise.allSettled(
1742
1656
  apiKeysToRefund.map(async (apiKeyEntry) => {
1743
1657
  const fullApiKeyEntry = this.storageAdapter.getApiKey(
@@ -1761,77 +1675,6 @@ var BalanceManager = class {
1761
1675
  }
1762
1676
  }
1763
1677
  }
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
1678
  /**
1836
1679
  * Post topup request to provider API
1837
1680
  */
@@ -2876,38 +2719,54 @@ var createMemoryDriver = (seed) => {
2876
2719
  var isBun = () => {
2877
2720
  return typeof process.versions.bun !== "undefined";
2878
2721
  };
2879
- var createDatabase = (dbPath) => {
2722
+ var cachedDbModule = null;
2723
+ var loadDatabase = async (dbPath) => {
2880
2724
  if (isBun()) {
2881
2725
  throw new Error(
2882
- "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
2726
+ "SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
2883
2727
  );
2884
2728
  }
2885
- let Database = null;
2886
2729
  try {
2887
- Database = __require("better-sqlite3");
2730
+ if (!cachedDbModule) {
2731
+ cachedDbModule = (await import('better-sqlite3')).default;
2732
+ }
2733
+ return new cachedDbModule(dbPath);
2888
2734
  } catch (error) {
2889
2735
  throw new Error(
2890
2736
  `better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
2891
2737
  );
2892
2738
  }
2893
- return new Database(dbPath);
2894
2739
  };
2895
2740
  var createSqliteDriver = (options = {}) => {
2896
2741
  const dbPath = options.dbPath || "routstr.sqlite";
2897
2742
  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 (?, ?)
2743
+ let db;
2744
+ let selectStmt;
2745
+ let upsertStmt;
2746
+ let deleteStmt;
2747
+ const initDb = async () => {
2748
+ if (!db) {
2749
+ db = await loadDatabase(dbPath);
2750
+ db.exec(
2751
+ `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
2752
+ );
2753
+ selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
2754
+ upsertStmt = db.prepare(
2755
+ `INSERT INTO ${tableName} (key, value) VALUES (?, ?)
2905
2756
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
2906
- );
2907
- const deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2757
+ );
2758
+ deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2759
+ }
2760
+ };
2761
+ const ensureInit = async () => {
2762
+ if (!db) {
2763
+ await initDb();
2764
+ }
2765
+ };
2908
2766
  return {
2909
2767
  async getItem(key, defaultValue) {
2910
2768
  try {
2769
+ await ensureInit();
2911
2770
  const row = selectStmt.get(key);
2912
2771
  if (!row || typeof row.value !== "string") return defaultValue;
2913
2772
  try {
@@ -2925,6 +2784,7 @@ var createSqliteDriver = (options = {}) => {
2925
2784
  },
2926
2785
  async setItem(key, value) {
2927
2786
  try {
2787
+ await ensureInit();
2928
2788
  upsertStmt.run(key, JSON.stringify(value));
2929
2789
  } catch (error) {
2930
2790
  console.error(`SQLite setItem failed for key "${key}":`, error);
@@ -2932,6 +2792,7 @@ var createSqliteDriver = (options = {}) => {
2932
2792
  },
2933
2793
  async removeItem(key) {
2934
2794
  try {
2795
+ await ensureInit();
2935
2796
  deleteStmt.run(key);
2936
2797
  } catch (error) {
2937
2798
  console.error(`SQLite removeItem failed for key "${key}":`, error);
@@ -2939,6 +2800,54 @@ var createSqliteDriver = (options = {}) => {
2939
2800
  }
2940
2801
  };
2941
2802
  };
2803
+ async function createBunSqliteDriver(dbPath) {
2804
+ const SQLite = (await import(
2805
+ /* webpackIgnore: true */
2806
+ 'bun:sqlite'
2807
+ )).default;
2808
+ const db = new SQLite(dbPath);
2809
+ db.run(`
2810
+ CREATE TABLE IF NOT EXISTS sdk_storage (
2811
+ key TEXT PRIMARY KEY,
2812
+ value TEXT NOT NULL
2813
+ )
2814
+ `);
2815
+ return {
2816
+ async getItem(key, defaultValue) {
2817
+ try {
2818
+ const row = db.query("SELECT value FROM sdk_storage WHERE key = ?").get(key);
2819
+ if (!row || typeof row.value !== "string") return defaultValue;
2820
+ try {
2821
+ return JSON.parse(row.value);
2822
+ } catch (parseError) {
2823
+ if (typeof defaultValue === "string") {
2824
+ return row.value;
2825
+ }
2826
+ throw parseError;
2827
+ }
2828
+ } catch (error) {
2829
+ console.error(`SQLite getItem failed for key "${key}":`, error);
2830
+ return defaultValue;
2831
+ }
2832
+ },
2833
+ async setItem(key, value) {
2834
+ try {
2835
+ db.query(
2836
+ "INSERT INTO sdk_storage (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
2837
+ ).run(key, JSON.stringify(value));
2838
+ } catch (error) {
2839
+ console.error(`SQLite setItem failed for key "${key}":`, error);
2840
+ }
2841
+ },
2842
+ async removeItem(key) {
2843
+ try {
2844
+ db.query("DELETE FROM sdk_storage WHERE key = ?").run(key);
2845
+ } catch (error) {
2846
+ console.error(`SQLite removeItem failed for key "${key}":`, error);
2847
+ }
2848
+ }
2849
+ };
2850
+ }
2942
2851
 
2943
2852
  // storage/drivers/indexedDB.ts
2944
2853
  var isBrowser = typeof indexedDB !== "undefined";
@@ -3044,9 +2953,9 @@ var SDK_STORAGE_KEYS = {
3044
2953
  INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
3045
2954
  LAST_MODELS_UPDATE: "lastModelsUpdate",
3046
2955
  LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
3047
- LOCAL_CASHU_TOKENS: "local_cashu_tokens",
3048
2956
  API_KEYS: "api_keys",
3049
2957
  CHILD_KEYS: "child_keys",
2958
+ XCASHU_TOKENS: "xcashu_tokens",
3050
2959
  ROUTSTR21_MODELS: "routstr21Models",
3051
2960
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
3052
2961
  CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
@@ -3237,21 +3146,23 @@ var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUr
3237
3146
  var isBun2 = () => {
3238
3147
  return typeof process.versions.bun !== "undefined";
3239
3148
  };
3240
- var createDatabase2 = (dbPath) => {
3149
+ var cachedDbModule2 = null;
3150
+ var loadDatabase2 = async (dbPath) => {
3241
3151
  if (isBun2()) {
3242
3152
  throw new Error(
3243
3153
  "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
3244
3154
  );
3245
3155
  }
3246
- let Database = null;
3247
3156
  try {
3248
- Database = __require("better-sqlite3");
3157
+ if (!cachedDbModule2) {
3158
+ cachedDbModule2 = (await import('better-sqlite3')).default;
3159
+ }
3160
+ return new cachedDbModule2(dbPath);
3249
3161
  } catch (error) {
3250
3162
  throw new Error(
3251
3163
  `better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
3252
3164
  );
3253
3165
  }
3254
- return new Database(dbPath);
3255
3166
  };
3256
3167
  var buildWhereClause = (options = {}) => {
3257
3168
  const clauses = [];
@@ -3288,38 +3199,49 @@ var buildWhereClause = (options = {}) => {
3288
3199
  var createSqliteUsageTrackingDriver = (options = {}) => {
3289
3200
  const dbPath = options.dbPath || "routstr.sqlite";
3290
3201
  const tableName = options.tableName || "usage_tracking";
3291
- const db = createDatabase2(dbPath);
3292
3202
  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
- `);
3203
+ let db;
3204
+ let insertStmt;
3322
3205
  let migrationComplete = false;
3206
+ const initDb = async () => {
3207
+ if (!db) {
3208
+ db = await loadDatabase2(dbPath);
3209
+ db.exec(`
3210
+ CREATE TABLE IF NOT EXISTS ${tableName} (
3211
+ id TEXT PRIMARY KEY,
3212
+ timestamp INTEGER NOT NULL,
3213
+ model_id TEXT NOT NULL,
3214
+ base_url TEXT NOT NULL,
3215
+ request_id TEXT NOT NULL,
3216
+ cost REAL NOT NULL,
3217
+ sats_cost REAL NOT NULL,
3218
+ prompt_tokens INTEGER NOT NULL,
3219
+ completion_tokens INTEGER NOT NULL,
3220
+ total_tokens INTEGER NOT NULL,
3221
+ client TEXT,
3222
+ session_id TEXT,
3223
+ tags TEXT
3224
+ );
3225
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
3226
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
3227
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
3228
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
3229
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
3230
+ `);
3231
+ insertStmt = db.prepare(`
3232
+ INSERT OR REPLACE INTO ${tableName} (
3233
+ id, timestamp, model_id, base_url, request_id,
3234
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
3235
+ client, session_id, tags
3236
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3237
+ `);
3238
+ }
3239
+ };
3240
+ const ensureInit = async () => {
3241
+ if (!db) {
3242
+ await initDb();
3243
+ }
3244
+ };
3323
3245
  const appendOne = (entry) => {
3324
3246
  insertStmt.run(
3325
3247
  entry.id,
@@ -3377,19 +3299,23 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
3377
3299
  });
3378
3300
  return {
3379
3301
  async migrate() {
3302
+ await ensureInit();
3380
3303
  await ensureMigrated();
3381
3304
  },
3382
3305
  async append(entry) {
3306
+ await ensureInit();
3383
3307
  await ensureMigrated();
3384
3308
  appendOne(entry);
3385
3309
  },
3386
3310
  async appendMany(entries) {
3311
+ await ensureInit();
3387
3312
  await ensureMigrated();
3388
3313
  for (const entry of entries) {
3389
3314
  appendOne(entry);
3390
3315
  }
3391
3316
  },
3392
3317
  async list(options2 = {}) {
3318
+ await ensureInit();
3393
3319
  await ensureMigrated();
3394
3320
  const { sql, params } = buildWhereClause(options2);
3395
3321
  const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
@@ -3402,6 +3328,7 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
3402
3328
  return rows.map(mapRow);
3403
3329
  },
3404
3330
  async count(options2 = {}) {
3331
+ await ensureInit();
3405
3332
  await ensureMigrated();
3406
3333
  const { sql, params } = buildWhereClause(options2);
3407
3334
  const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
@@ -3409,20 +3336,197 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
3409
3336
  return Number(row?.count ?? 0);
3410
3337
  },
3411
3338
  async deleteOlderThan(timestamp) {
3339
+ await ensureInit();
3412
3340
  await ensureMigrated();
3413
3341
  const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
3414
3342
  const result = stmt.run(timestamp);
3415
3343
  return result.changes;
3416
3344
  },
3417
3345
  async clear() {
3346
+ await ensureInit();
3418
3347
  await ensureMigrated();
3419
3348
  db.prepare(`DELETE FROM ${tableName}`).run();
3420
3349
  }
3421
3350
  };
3422
3351
  };
3423
3352
 
3424
- // storage/usageTracking/memory.ts
3353
+ // storage/usageTracking/bunSqlite.ts
3354
+ var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
3425
3355
  var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3356
+ var buildWhereClause2 = (options = {}) => {
3357
+ const clauses = [];
3358
+ const params = [];
3359
+ if (typeof options.before === "number") {
3360
+ clauses.push("timestamp < ?");
3361
+ params.push(options.before);
3362
+ }
3363
+ if (typeof options.after === "number") {
3364
+ clauses.push("timestamp > ?");
3365
+ params.push(options.after);
3366
+ }
3367
+ if (options.modelId) {
3368
+ clauses.push("model_id = ?");
3369
+ params.push(options.modelId);
3370
+ }
3371
+ if (options.baseUrl) {
3372
+ clauses.push("base_url = ?");
3373
+ params.push(normalizeBaseUrl3(options.baseUrl));
3374
+ }
3375
+ if (options.sessionId) {
3376
+ clauses.push("session_id = ?");
3377
+ params.push(options.sessionId);
3378
+ }
3379
+ if (options.client) {
3380
+ clauses.push("client = ?");
3381
+ params.push(options.client);
3382
+ }
3383
+ return {
3384
+ sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
3385
+ params
3386
+ };
3387
+ };
3388
+ var createBunSqliteUsageTrackingDriver = (options = {}) => {
3389
+ const dbPath = options.dbPath || "routstr.sqlite";
3390
+ const tableName = options.tableName || "usage_tracking";
3391
+ const legacyStorageDriver = options.legacyStorageDriver;
3392
+ const SQLiteDatabase = options.sqlite?.Database;
3393
+ let migrationPromise = null;
3394
+ if (!SQLiteDatabase) {
3395
+ throw new Error(
3396
+ "Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
3397
+ );
3398
+ }
3399
+ const db = new SQLiteDatabase(dbPath);
3400
+ db.run(`
3401
+ CREATE TABLE IF NOT EXISTS ${tableName} (
3402
+ id TEXT PRIMARY KEY,
3403
+ timestamp INTEGER NOT NULL,
3404
+ model_id TEXT NOT NULL,
3405
+ base_url TEXT NOT NULL,
3406
+ request_id TEXT NOT NULL,
3407
+ cost REAL NOT NULL,
3408
+ sats_cost REAL NOT NULL,
3409
+ prompt_tokens INTEGER NOT NULL,
3410
+ completion_tokens INTEGER NOT NULL,
3411
+ total_tokens INTEGER NOT NULL,
3412
+ client TEXT,
3413
+ session_id TEXT,
3414
+ tags TEXT
3415
+ )
3416
+ `);
3417
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
3418
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
3419
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
3420
+ const appendOne = (entry) => {
3421
+ db.query(`
3422
+ INSERT OR REPLACE INTO ${tableName} (
3423
+ id, timestamp, model_id, base_url, request_id,
3424
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
3425
+ client, session_id, tags
3426
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3427
+ `).run(
3428
+ entry.id,
3429
+ entry.timestamp,
3430
+ entry.modelId,
3431
+ normalizeBaseUrl3(entry.baseUrl),
3432
+ entry.requestId,
3433
+ entry.cost,
3434
+ entry.satsCost,
3435
+ entry.promptTokens,
3436
+ entry.completionTokens,
3437
+ entry.totalTokens,
3438
+ entry.client ?? null,
3439
+ entry.sessionId ?? null,
3440
+ JSON.stringify(entry.tags ?? [])
3441
+ );
3442
+ };
3443
+ const mapRow = (row) => ({
3444
+ id: row.id,
3445
+ timestamp: row.timestamp,
3446
+ modelId: row.model_id,
3447
+ baseUrl: row.base_url,
3448
+ requestId: row.request_id,
3449
+ cost: row.cost,
3450
+ satsCost: row.sats_cost,
3451
+ promptTokens: row.prompt_tokens,
3452
+ completionTokens: row.completion_tokens,
3453
+ totalTokens: row.total_tokens,
3454
+ client: row.client ?? void 0,
3455
+ sessionId: row.session_id ?? void 0,
3456
+ tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
3457
+ });
3458
+ const ensureMigrated = async () => {
3459
+ if (!legacyStorageDriver) return;
3460
+ if (!migrationPromise) {
3461
+ migrationPromise = (async () => {
3462
+ const migrated = await legacyStorageDriver.getItem(
3463
+ MIGRATION_MARKER_KEY3,
3464
+ false
3465
+ );
3466
+ if (migrated) return;
3467
+ const legacyEntries = await legacyStorageDriver.getItem(
3468
+ SDK_STORAGE_KEYS.USAGE_TRACKING,
3469
+ []
3470
+ );
3471
+ if (legacyEntries.length > 0) {
3472
+ for (const entry of legacyEntries) {
3473
+ appendOne(entry);
3474
+ }
3475
+ await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
3476
+ }
3477
+ await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
3478
+ })();
3479
+ }
3480
+ await migrationPromise;
3481
+ };
3482
+ return {
3483
+ async migrate() {
3484
+ await ensureMigrated();
3485
+ },
3486
+ async append(entry) {
3487
+ await ensureMigrated();
3488
+ appendOne(entry);
3489
+ },
3490
+ async appendMany(entries) {
3491
+ await ensureMigrated();
3492
+ for (const entry of entries) {
3493
+ appendOne(entry);
3494
+ }
3495
+ },
3496
+ async list(options2 = {}) {
3497
+ await ensureMigrated();
3498
+ const { sql, params } = buildWhereClause2(options2);
3499
+ const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
3500
+ const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
3501
+ let rows;
3502
+ if (typeof options2.limit === "number") {
3503
+ rows = db.query(query).all(...params, options2.limit);
3504
+ } else {
3505
+ rows = db.query(query).all(...params);
3506
+ }
3507
+ return rows.map(mapRow);
3508
+ },
3509
+ async count(options2 = {}) {
3510
+ const { sql, params } = buildWhereClause2(options2);
3511
+ const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
3512
+ const row = db.query(query).get(...params);
3513
+ return Number(row?.count ?? 0);
3514
+ },
3515
+ async deleteOlderThan(timestamp) {
3516
+ await ensureMigrated();
3517
+ const before = timestamp;
3518
+ const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
3519
+ return result.changes ?? 0;
3520
+ },
3521
+ async clear() {
3522
+ await ensureMigrated();
3523
+ db.query(`DELETE FROM ${tableName}`).run();
3524
+ }
3525
+ };
3526
+ };
3527
+
3528
+ // storage/usageTracking/memory.ts
3529
+ var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3426
3530
  var matchesFilters2 = (entry, options = {}) => {
3427
3531
  if (typeof options.before === "number" && entry.timestamp >= options.before) {
3428
3532
  return false;
@@ -3433,7 +3537,7 @@ var matchesFilters2 = (entry, options = {}) => {
3433
3537
  if (options.modelId && entry.modelId !== options.modelId) {
3434
3538
  return false;
3435
3539
  }
3436
- if (options.baseUrl && normalizeBaseUrl3(entry.baseUrl) !== normalizeBaseUrl3(options.baseUrl)) {
3540
+ if (options.baseUrl && normalizeBaseUrl4(entry.baseUrl) !== normalizeBaseUrl4(options.baseUrl)) {
3437
3541
  return false;
3438
3542
  }
3439
3543
  if (options.sessionId && entry.sessionId !== options.sessionId) {
@@ -3447,18 +3551,18 @@ var matchesFilters2 = (entry, options = {}) => {
3447
3551
  var createMemoryUsageTrackingDriver = (seed = []) => {
3448
3552
  const store = /* @__PURE__ */ new Map();
3449
3553
  for (const entry of seed) {
3450
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
3554
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
3451
3555
  }
3452
3556
  return {
3453
3557
  async migrate() {
3454
3558
  return;
3455
3559
  },
3456
3560
  async append(entry) {
3457
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
3561
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
3458
3562
  },
3459
3563
  async appendMany(entries) {
3460
3564
  for (const entry of entries) {
3461
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
3565
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
3462
3566
  }
3463
3567
  },
3464
3568
  async list(options = {}) {
@@ -3486,20 +3590,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
3486
3590
  }
3487
3591
  };
3488
3592
  };
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
- };
3593
+ var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3503
3594
  var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3504
3595
  modelsFromAllProviders: {},
3505
3596
  lastUsedModel: null,
@@ -3509,9 +3600,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3509
3600
  mintsFromAllProviders: {},
3510
3601
  infoFromAllProviders: {},
3511
3602
  lastModelsUpdate: {},
3512
- cachedTokens: [],
3513
3603
  apiKeys: [],
3514
3604
  childKeys: [],
3605
+ xcashuTokens: {},
3515
3606
  routstr21Models: [],
3516
3607
  lastRoutstr21ModelsUpdate: null,
3517
3608
  cachedReceiveTokens: [],
@@ -3519,7 +3610,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3519
3610
  setModelsFromAllProviders: (value) => {
3520
3611
  const normalized = {};
3521
3612
  for (const [baseUrl, models] of Object.entries(value)) {
3522
- normalized[normalizeBaseUrl4(baseUrl)] = models;
3613
+ normalized[normalizeBaseUrl5(baseUrl)] = models;
3523
3614
  }
3524
3615
  void driver.setItem(
3525
3616
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3532,7 +3623,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3532
3623
  set({ lastUsedModel: value });
3533
3624
  },
3534
3625
  setBaseUrlsList: (value) => {
3535
- const normalized = value.map((url) => normalizeBaseUrl4(url));
3626
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3536
3627
  void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
3537
3628
  set({ baseUrlsList: normalized });
3538
3629
  },
@@ -3541,14 +3632,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3541
3632
  set({ lastBaseUrlsUpdate: value });
3542
3633
  },
3543
3634
  setDisabledProviders: (value) => {
3544
- const normalized = value.map((url) => normalizeBaseUrl4(url));
3635
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3545
3636
  void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
3546
3637
  set({ disabledProviders: normalized });
3547
3638
  },
3548
3639
  setMintsFromAllProviders: (value) => {
3549
3640
  const normalized = {};
3550
3641
  for (const [baseUrl, mints] of Object.entries(value)) {
3551
- normalized[normalizeBaseUrl4(baseUrl)] = mints.map(
3642
+ normalized[normalizeBaseUrl5(baseUrl)] = mints.map(
3552
3643
  (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
3553
3644
  );
3554
3645
  }
@@ -3561,7 +3652,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3561
3652
  setInfoFromAllProviders: (value) => {
3562
3653
  const normalized = {};
3563
3654
  for (const [baseUrl, info] of Object.entries(value)) {
3564
- normalized[normalizeBaseUrl4(baseUrl)] = info;
3655
+ normalized[normalizeBaseUrl5(baseUrl)] = info;
3565
3656
  }
3566
3657
  void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
3567
3658
  set({ infoFromAllProviders: normalized });
@@ -3569,30 +3660,17 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3569
3660
  setLastModelsUpdate: (value) => {
3570
3661
  const normalized = {};
3571
3662
  for (const [baseUrl, timestamp] of Object.entries(value)) {
3572
- normalized[normalizeBaseUrl4(baseUrl)] = timestamp;
3663
+ normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
3573
3664
  }
3574
3665
  void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
3575
3666
  set({ lastModelsUpdate: normalized });
3576
3667
  },
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
3668
  setApiKeys: (value) => {
3591
3669
  set((state) => {
3592
3670
  const updates = typeof value === "function" ? value(state.apiKeys) : value;
3593
3671
  const normalized = updates.map((entry) => ({
3594
3672
  ...entry,
3595
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
3673
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3596
3674
  balance: entry.balance ?? 0,
3597
3675
  lastUsed: entry.lastUsed ?? null
3598
3676
  }));
@@ -3604,7 +3682,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3604
3682
  set((state) => {
3605
3683
  const updates = typeof value === "function" ? value(state.childKeys) : value;
3606
3684
  const normalized = updates.map((entry) => ({
3607
- parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
3685
+ parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
3608
3686
  childKey: entry.childKey,
3609
3687
  balance: entry.balance ?? 0,
3610
3688
  balanceLimit: entry.balanceLimit,
@@ -3615,6 +3693,30 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
3615
3693
  return { childKeys: normalized };
3616
3694
  });
3617
3695
  },
3696
+ setXcashuTokens: (value) => {
3697
+ const normalized = {};
3698
+ for (const [baseUrl, tokens] of Object.entries(value)) {
3699
+ normalized[normalizeBaseUrl5(baseUrl)] = tokens.map((entry) => ({
3700
+ ...entry,
3701
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3702
+ createdAt: entry.createdAt ?? Date.now(),
3703
+ tryCount: entry.tryCount ?? 0
3704
+ }));
3705
+ }
3706
+ void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, normalized);
3707
+ set({ xcashuTokens: normalized });
3708
+ },
3709
+ updateXcashuTokenTryCount: (token, tryCount) => {
3710
+ const currentTokens = get().xcashuTokens;
3711
+ const updatedTokens = {};
3712
+ for (const [baseUrl, tokens] of Object.entries(currentTokens)) {
3713
+ updatedTokens[baseUrl] = tokens.map(
3714
+ (entry) => entry.token === token ? { ...entry, tryCount } : entry
3715
+ );
3716
+ }
3717
+ void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, updatedTokens);
3718
+ set({ xcashuTokens: updatedTokens });
3719
+ },
3618
3720
  setRoutstr21Models: (value) => {
3619
3721
  void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
3620
3722
  set({ routstr21Models: value });
@@ -3656,9 +3758,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
3656
3758
  rawMints,
3657
3759
  rawInfo,
3658
3760
  rawLastModelsUpdate,
3659
- rawCachedTokens,
3660
3761
  rawApiKeys,
3661
3762
  rawChildKeys,
3763
+ rawXcashuTokens,
3662
3764
  rawRoutstr21Models,
3663
3765
  rawLastRoutstr21ModelsUpdate,
3664
3766
  rawCachedReceiveTokens,
@@ -3684,9 +3786,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
3684
3786
  SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
3685
3787
  {}
3686
3788
  ),
3687
- driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
3688
3789
  driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
3689
3790
  driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
3791
+ driver.getItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, {}),
3690
3792
  driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
3691
3793
  driver.getItem(
3692
3794
  SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
@@ -3697,52 +3799,57 @@ var hydrateStoreFromDriver = async (store, driver) => {
3697
3799
  ]);
3698
3800
  const modelsFromAllProviders = Object.fromEntries(
3699
3801
  Object.entries(rawModels).map(([baseUrl, models]) => [
3700
- normalizeBaseUrl4(baseUrl),
3802
+ normalizeBaseUrl5(baseUrl),
3701
3803
  models
3702
3804
  ])
3703
3805
  );
3704
- const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl4(url));
3806
+ const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl5(url));
3705
3807
  const disabledProviders = rawDisabledProviders.map(
3706
- (url) => normalizeBaseUrl4(url)
3808
+ (url) => normalizeBaseUrl5(url)
3707
3809
  );
3708
3810
  const mintsFromAllProviders = Object.fromEntries(
3709
3811
  Object.entries(rawMints).map(([baseUrl, mints]) => [
3710
- normalizeBaseUrl4(baseUrl),
3812
+ normalizeBaseUrl5(baseUrl),
3711
3813
  mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
3712
3814
  ])
3713
3815
  );
3714
3816
  const infoFromAllProviders = Object.fromEntries(
3715
3817
  Object.entries(rawInfo).map(([baseUrl, info]) => [
3716
- normalizeBaseUrl4(baseUrl),
3818
+ normalizeBaseUrl5(baseUrl),
3717
3819
  info
3718
3820
  ])
3719
3821
  );
3720
3822
  const lastModelsUpdate = Object.fromEntries(
3721
3823
  Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
3722
- normalizeBaseUrl4(baseUrl),
3824
+ normalizeBaseUrl5(baseUrl),
3723
3825
  timestamp
3724
3826
  ])
3725
3827
  );
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
3828
  const apiKeys = rawApiKeys.map((entry) => ({
3733
3829
  ...entry,
3734
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
3830
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3735
3831
  balance: entry.balance ?? 0,
3736
3832
  lastUsed: entry.lastUsed ?? null
3737
3833
  }));
3738
3834
  const childKeys = rawChildKeys.map((entry) => ({
3739
- parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
3835
+ parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
3740
3836
  childKey: entry.childKey,
3741
3837
  balance: entry.balance ?? 0,
3742
3838
  balanceLimit: entry.balanceLimit,
3743
3839
  validityDate: entry.validityDate,
3744
3840
  createdAt: entry.createdAt ?? Date.now()
3745
3841
  }));
3842
+ const xcashuTokens = Object.fromEntries(
3843
+ Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
3844
+ normalizeBaseUrl5(baseUrl),
3845
+ tokens.map((entry) => ({
3846
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3847
+ token: entry.token,
3848
+ createdAt: entry.createdAt ?? Date.now(),
3849
+ tryCount: entry.tryCount ?? 0
3850
+ }))
3851
+ ])
3852
+ );
3746
3853
  const routstr21Models = rawRoutstr21Models;
3747
3854
  const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
3748
3855
  const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
@@ -3765,9 +3872,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
3765
3872
  mintsFromAllProviders,
3766
3873
  infoFromAllProviders,
3767
3874
  lastModelsUpdate,
3768
- cachedTokens,
3769
3875
  apiKeys,
3770
3876
  childKeys,
3877
+ xcashuTokens,
3771
3878
  routstr21Models,
3772
3879
  lastRoutstr21ModelsUpdate,
3773
3880
  cachedReceiveTokens,
@@ -3791,12 +3898,12 @@ var createDiscoveryAdapterFromStore = (store) => ({
3791
3898
  getCachedProviderInfo: () => store.getState().infoFromAllProviders,
3792
3899
  setCachedProviderInfo: (info) => store.getState().setInfoFromAllProviders(info),
3793
3900
  getProviderLastUpdate: (baseUrl) => {
3794
- const normalized = normalizeBaseUrl4(baseUrl);
3901
+ const normalized = normalizeBaseUrl5(baseUrl);
3795
3902
  const timestamps = store.getState().lastModelsUpdate;
3796
3903
  return timestamps[normalized] || null;
3797
3904
  },
3798
3905
  setProviderLastUpdate: (baseUrl, timestamp) => {
3799
- const normalized = normalizeBaseUrl4(baseUrl);
3906
+ const normalized = normalizeBaseUrl5(baseUrl);
3800
3907
  const timestamps = { ...store.getState().lastModelsUpdate };
3801
3908
  timestamps[normalized] = timestamp;
3802
3909
  store.getState().setLastModelsUpdate(timestamps);
@@ -3814,59 +3921,6 @@ var createDiscoveryAdapterFromStore = (store) => ({
3814
3921
  setRoutstr21ModelsLastUpdate: (timestamp) => store.getState().setRoutstr21ModelsLastUpdate(timestamp)
3815
3922
  });
3816
3923
  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
3924
  getApiKeyDistribution: () => {
3871
3925
  const apiKeys = store.getState().apiKeys;
3872
3926
  const distributionMap = {};
@@ -3879,28 +3933,24 @@ var createStorageAdapterFromStore = (store) => ({
3879
3933
  return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
3880
3934
  },
3881
3935
  saveProviderInfo: (baseUrl, info) => {
3882
- const normalized = normalizeBaseUrl4(baseUrl);
3936
+ const normalized = normalizeBaseUrl5(baseUrl);
3883
3937
  const next = { ...store.getState().infoFromAllProviders };
3884
3938
  next[normalized] = info;
3885
3939
  store.getState().setInfoFromAllProviders(next);
3886
3940
  },
3887
3941
  getProviderInfo: (baseUrl) => {
3888
- const normalized = normalizeBaseUrl4(baseUrl);
3942
+ const normalized = normalizeBaseUrl5(baseUrl);
3889
3943
  return store.getState().infoFromAllProviders[normalized] || null;
3890
3944
  },
3891
3945
  // ========== API Keys (for apikeys mode) ==========
3892
3946
  getApiKey: (baseUrl) => {
3893
- const normalized = normalizeBaseUrl4(baseUrl);
3947
+ const normalized = normalizeBaseUrl5(baseUrl);
3894
3948
  const entry = store.getState().apiKeys.find((key) => key.baseUrl === normalized);
3895
3949
  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
3950
  return entry;
3901
3951
  },
3902
3952
  setApiKey: (baseUrl, key) => {
3903
- const normalized = normalizeBaseUrl4(baseUrl);
3953
+ const normalized = normalizeBaseUrl5(baseUrl);
3904
3954
  const keys = store.getState().apiKeys;
3905
3955
  const existingIndex = keys.findIndex(
3906
3956
  (entry) => entry.baseUrl === normalized
@@ -3918,15 +3968,15 @@ var createStorageAdapterFromStore = (store) => ({
3918
3968
  store.getState().setApiKeys(next);
3919
3969
  },
3920
3970
  updateApiKeyBalance: (baseUrl, balance) => {
3921
- const normalized = normalizeBaseUrl4(baseUrl);
3971
+ const normalized = normalizeBaseUrl5(baseUrl);
3922
3972
  const keys = store.getState().apiKeys;
3923
3973
  const next = keys.map(
3924
- (entry) => entry.baseUrl === normalized ? { ...entry, balance } : entry
3974
+ (entry) => entry.baseUrl === normalized ? { ...entry, balance, lastUsed: Date.now() } : entry
3925
3975
  );
3926
3976
  store.getState().setApiKeys(next);
3927
3977
  },
3928
3978
  removeApiKey: (baseUrl) => {
3929
- const normalized = normalizeBaseUrl4(baseUrl);
3979
+ const normalized = normalizeBaseUrl5(baseUrl);
3930
3980
  const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
3931
3981
  store.getState().setApiKeys(next);
3932
3982
  },
@@ -3940,7 +3990,7 @@ var createStorageAdapterFromStore = (store) => ({
3940
3990
  },
3941
3991
  // ========== Child Keys ==========
3942
3992
  getChildKey: (parentBaseUrl) => {
3943
- const normalized = normalizeBaseUrl4(parentBaseUrl);
3993
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3944
3994
  const entry = store.getState().childKeys.find((key) => key.parentBaseUrl === normalized);
3945
3995
  if (!entry) return null;
3946
3996
  return {
@@ -3953,7 +4003,7 @@ var createStorageAdapterFromStore = (store) => ({
3953
4003
  };
3954
4004
  },
3955
4005
  setChildKey: (parentBaseUrl, childKey, balance, validityDate, balanceLimit) => {
3956
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4006
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3957
4007
  const keys = store.getState().childKeys;
3958
4008
  const existingIndex = keys.findIndex(
3959
4009
  (entry) => entry.parentBaseUrl === normalized
@@ -3984,7 +4034,7 @@ var createStorageAdapterFromStore = (store) => ({
3984
4034
  }
3985
4035
  },
3986
4036
  updateChildKeyBalance: (parentBaseUrl, balance) => {
3987
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4037
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3988
4038
  const keys = store.getState().childKeys;
3989
4039
  const next = keys.map(
3990
4040
  (entry) => entry.parentBaseUrl === normalized ? { ...entry, balance } : entry
@@ -3992,7 +4042,7 @@ var createStorageAdapterFromStore = (store) => ({
3992
4042
  store.getState().setChildKeys(next);
3993
4043
  },
3994
4044
  removeChildKey: (parentBaseUrl) => {
3995
- const normalized = normalizeBaseUrl4(parentBaseUrl);
4045
+ const normalized = normalizeBaseUrl5(parentBaseUrl);
3996
4046
  const next = store.getState().childKeys.filter((entry) => entry.parentBaseUrl !== normalized);
3997
4047
  store.getState().setChildKeys(next);
3998
4048
  },
@@ -4011,20 +4061,60 @@ var createStorageAdapterFromStore = (store) => ({
4011
4061
  },
4012
4062
  setCachedReceiveTokens: (tokens) => {
4013
4063
  store.getState().setCachedReceiveTokens(tokens);
4064
+ },
4065
+ // ========== XCashu Tokens (multiple tokens per baseUrl) ==========
4066
+ getXcashuTokens: () => {
4067
+ return store.getState().xcashuTokens;
4068
+ },
4069
+ getXcashuTokensForBaseUrl: (baseUrl) => {
4070
+ const normalized = normalizeBaseUrl5(baseUrl);
4071
+ return store.getState().xcashuTokens[normalized] || [];
4072
+ },
4073
+ addXcashuToken: (baseUrl, token) => {
4074
+ const normalized = normalizeBaseUrl5(baseUrl);
4075
+ const tokens = store.getState().xcashuTokens;
4076
+ const existing = tokens[normalized] || [];
4077
+ const next = { ...tokens };
4078
+ next[normalized] = [
4079
+ ...existing,
4080
+ { baseUrl: normalized, token, createdAt: Date.now(), tryCount: 0 }
4081
+ ];
4082
+ store.getState().setXcashuTokens(next);
4083
+ },
4084
+ removeXcashuToken: (baseUrl, token) => {
4085
+ const normalized = normalizeBaseUrl5(baseUrl);
4086
+ const tokens = store.getState().xcashuTokens;
4087
+ const existing = tokens[normalized] || [];
4088
+ const next = { ...tokens };
4089
+ next[normalized] = existing.filter((entry) => entry.token !== token);
4090
+ if (next[normalized].length === 0) {
4091
+ delete next[normalized];
4092
+ }
4093
+ store.getState().setXcashuTokens(next);
4094
+ },
4095
+ clearXcashuTokensForBaseUrl: (baseUrl) => {
4096
+ const normalized = normalizeBaseUrl5(baseUrl);
4097
+ const tokens = store.getState().xcashuTokens;
4098
+ const next = { ...tokens };
4099
+ delete next[normalized];
4100
+ store.getState().setXcashuTokens(next);
4101
+ },
4102
+ updateXcashuTokenTryCount: (token, tryCount) => {
4103
+ store.getState().updateXcashuTokenTryCount(token, tryCount);
4014
4104
  }
4015
4105
  });
4016
4106
  var createProviderRegistryFromStore = (store) => ({
4017
4107
  getModelsForProvider: (baseUrl) => {
4018
- const normalized = normalizeBaseUrl4(baseUrl);
4108
+ const normalized = normalizeBaseUrl5(baseUrl);
4019
4109
  return store.getState().modelsFromAllProviders[normalized] || [];
4020
4110
  },
4021
4111
  getDisabledProviders: () => store.getState().disabledProviders,
4022
4112
  getProviderMints: (baseUrl) => {
4023
- const normalized = normalizeBaseUrl4(baseUrl);
4113
+ const normalized = normalizeBaseUrl5(baseUrl);
4024
4114
  return store.getState().mintsFromAllProviders[normalized] || [];
4025
4115
  },
4026
4116
  getProviderInfo: async (baseUrl) => {
4027
- const normalized = normalizeBaseUrl4(baseUrl);
4117
+ const normalized = normalizeBaseUrl5(baseUrl);
4028
4118
  const cached = store.getState().infoFromAllProviders[normalized];
4029
4119
  if (cached) return cached;
4030
4120
  try {
@@ -4099,7 +4189,7 @@ var getDefaultUsageTrackingDriver = () => {
4099
4189
  return defaultUsageTrackingDriver;
4100
4190
  }
4101
4191
  if (isBun3()) {
4102
- defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
4192
+ defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
4103
4193
  return defaultUsageTrackingDriver;
4104
4194
  }
4105
4195
  if (isNode()) {
@@ -4111,21 +4201,28 @@ var getDefaultUsageTrackingDriver = () => {
4111
4201
  defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
4112
4202
  return defaultUsageTrackingDriver;
4113
4203
  };
4204
+ var setDefaultUsageTrackingDriver = (driver) => {
4205
+ defaultUsageTrackingDriver = driver;
4206
+ };
4114
4207
  var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
4115
4208
  var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
4116
4209
  var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
4117
4210
  function createSSEParserTransform(onUsage, onResponseId) {
4118
4211
  let buffer = "";
4212
+ let usageCaptured = false;
4213
+ let responseIdCaptured = false;
4119
4214
  const maybeCaptureUsageFromJson = (jsonText) => {
4120
4215
  try {
4121
4216
  const data = JSON.parse(jsonText);
4122
4217
  const responseId = data.id;
4123
4218
  if (typeof responseId === "string" && responseId.trim().length > 0) {
4124
4219
  onResponseId?.(responseId.trim());
4220
+ responseIdCaptured = true;
4125
4221
  }
4126
4222
  const usage = extractUsageFromSSEJson(data);
4127
4223
  if (usage) {
4128
4224
  onUsage(usage);
4225
+ usageCaptured = true;
4129
4226
  }
4130
4227
  } catch {
4131
4228
  }
@@ -4181,7 +4278,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
4181
4278
  }
4182
4279
  var TOPUP_MARGIN = 1.2;
4183
4280
  var RoutstrClient = class {
4184
- constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
4281
+ constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
4185
4282
  this.walletAdapter = walletAdapter;
4186
4283
  this.storageAdapter = storageAdapter;
4187
4284
  this.providerRegistry = providerRegistry;
@@ -4199,13 +4296,9 @@ var RoutstrClient = class {
4199
4296
  this.streamProcessor = new StreamProcessor();
4200
4297
  this.providerManager = new ProviderManager(providerRegistry);
4201
4298
  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
- }
4299
+ this.mode = mode;
4300
+ this.usageTrackingDriver = options.usageTrackingDriver;
4301
+ this.sdkStore = options.sdkStore;
4209
4302
  }
4210
4303
  cashuSpender;
4211
4304
  balanceManager;
@@ -4214,6 +4307,8 @@ var RoutstrClient = class {
4214
4307
  alertLevel;
4215
4308
  mode;
4216
4309
  debugLevel = "WARN";
4310
+ usageTrackingDriver;
4311
+ sdkStore;
4217
4312
  /**
4218
4313
  * Get the current client mode
4219
4314
  */
@@ -4283,11 +4378,13 @@ var RoutstrClient = class {
4283
4378
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4284
4379
  token: prepared.tokenUsed,
4285
4380
  baseUrl: prepared.baseUrlUsed,
4381
+ mintUrl: params.mintUrl,
4286
4382
  initialTokenBalance: prepared.tokenBalanceInSats,
4287
4383
  response: prepared.response,
4288
4384
  modelId: prepared.modelId,
4289
4385
  usage: prepared.capturedUsage,
4290
- requestId: prepared.capturedResponseId
4386
+ requestId: prepared.capturedResponseId,
4387
+ clientApiKey: prepared.clientApiKey
4291
4388
  });
4292
4389
  prepared.response.satsSpent = satsSpent;
4293
4390
  prepared.response.usage = prepared.capturedUsage;
@@ -4306,11 +4403,13 @@ var RoutstrClient = class {
4306
4403
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4307
4404
  token: prepared.tokenUsed,
4308
4405
  baseUrl: prepared.baseUrlUsed,
4406
+ mintUrl: params.mintUrl,
4309
4407
  initialTokenBalance: prepared.tokenBalanceInSats,
4310
4408
  response: prepared.response,
4311
4409
  modelId: prepared.modelId,
4312
4410
  usage: prepared.capturedUsage,
4313
- requestId: prepared.capturedResponseId
4411
+ requestId: prepared.capturedResponseId,
4412
+ clientApiKey: prepared.clientApiKey
4314
4413
  });
4315
4414
  prepared.response.satsSpent = satsSpent;
4316
4415
  res.end();
@@ -4326,11 +4425,13 @@ var RoutstrClient = class {
4326
4425
  const satsSpent = await this._handlePostResponseBalanceUpdate({
4327
4426
  token: prepared.tokenUsed,
4328
4427
  baseUrl: prepared.baseUrlUsed,
4428
+ mintUrl: params.mintUrl,
4329
4429
  initialTokenBalance: prepared.tokenBalanceInSats,
4330
4430
  response: prepared.response,
4331
4431
  modelId: prepared.modelId,
4332
4432
  usage: prepared.capturedUsage,
4333
- requestId: prepared.capturedResponseId
4433
+ requestId: prepared.capturedResponseId,
4434
+ clientApiKey: prepared.clientApiKey
4334
4435
  });
4335
4436
  prepared.response.satsSpent = satsSpent;
4336
4437
  prepared.response.usage = prepared.capturedUsage;
@@ -4360,8 +4461,10 @@ var RoutstrClient = class {
4360
4461
  headers = {},
4361
4462
  baseUrl,
4362
4463
  mintUrl,
4363
- modelId
4464
+ modelId,
4465
+ clientApiKey: providedClientApiKey
4364
4466
  } = params;
4467
+ const clientApiKey = providedClientApiKey ?? this._extractClientApiKey(headers);
4365
4468
  await this._checkBalance();
4366
4469
  let requiredSats = 1;
4367
4470
  let selectedModel;
@@ -4383,7 +4486,6 @@ var RoutstrClient = class {
4383
4486
  amount: requiredSats,
4384
4487
  baseUrl
4385
4488
  });
4386
- this._log("DEBUG", token, baseUrl);
4387
4489
  let requestBody = body;
4388
4490
  if (body && typeof body === "object") {
4389
4491
  const bodyObj = body;
@@ -4391,7 +4493,7 @@ var RoutstrClient = class {
4391
4493
  requestBody = { ...bodyObj, stream: false };
4392
4494
  }
4393
4495
  }
4394
- const baseHeaders = this._buildBaseHeaders(headers);
4496
+ const baseHeaders = this._buildBaseHeaders();
4395
4497
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
4396
4498
  const response = await this._makeRequest({
4397
4499
  path,
@@ -4443,9 +4545,21 @@ var RoutstrClient = class {
4443
4545
  tokenBalanceInSats,
4444
4546
  modelId,
4445
4547
  capturedUsage,
4446
- capturedResponseId
4548
+ capturedResponseId,
4549
+ clientApiKey
4447
4550
  };
4448
4551
  }
4552
+ /**
4553
+ * Extract clientApiKey from Authorization Bearer token if present
4554
+ */
4555
+ _extractClientApiKey(headers) {
4556
+ const authHeader = headers["Authorization"] || headers["authorization"];
4557
+ if (authHeader?.startsWith("Bearer ")) {
4558
+ const extractedKey = authHeader.slice(7);
4559
+ return extractedKey;
4560
+ }
4561
+ return void 0;
4562
+ }
4449
4563
  /**
4450
4564
  * Fetch AI response with streaming
4451
4565
  */
@@ -4549,6 +4663,7 @@ var RoutstrClient = class {
4549
4663
  let satsSpent = await this._handlePostResponseBalanceUpdate({
4550
4664
  token,
4551
4665
  baseUrl: baseUrlUsed,
4666
+ mintUrl,
4552
4667
  initialTokenBalance: tokenBalanceInSats,
4553
4668
  fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
4554
4669
  response,
@@ -4587,7 +4702,6 @@ var RoutstrClient = class {
4587
4702
  try {
4588
4703
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
4589
4704
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
4590
- this._log("DEBUG", "HEADERS,", headers);
4591
4705
  const response = await fetch(url, {
4592
4706
  method,
4593
4707
  headers,
@@ -4656,8 +4770,6 @@ var RoutstrClient = class {
4656
4770
  `[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
4657
4771
  );
4658
4772
  tryNextProvider = true;
4659
- if (this.mode === "lazyrefund")
4660
- this.storageAdapter.removeToken(baseUrl);
4661
4773
  } else {
4662
4774
  this._log(
4663
4775
  "DEBUG",
@@ -4705,22 +4817,15 @@ var RoutstrClient = class {
4705
4817
  );
4706
4818
  }
4707
4819
  }
4708
- if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
4820
+ if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
4709
4821
  this.storageAdapter.getApiKey(baseUrl);
4710
4822
  let topupAmount = params.requiredSats;
4711
4823
  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
- }
4824
+ const currentBalanceInfo = await this.balanceManager.getTokenBalance(
4825
+ params.token,
4826
+ baseUrl
4827
+ );
4828
+ const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
4724
4829
  const shortfall = Math.max(0, params.requiredSats - currentBalance);
4725
4830
  topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
4726
4831
  } catch (e) {
@@ -4857,34 +4962,7 @@ var RoutstrClient = class {
4857
4962
  "DEBUG",
4858
4963
  `[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
4859
4964
  );
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") {
4965
+ if (this.mode === "apikeys") {
4888
4966
  this._log(
4889
4967
  "DEBUG",
4890
4968
  `[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
@@ -4900,7 +4978,8 @@ var RoutstrClient = class {
4900
4978
  const refundResult = await this.balanceManager.refundApiKey({
4901
4979
  mintUrl,
4902
4980
  baseUrl,
4903
- apiKey: token
4981
+ apiKey: token,
4982
+ forceRefund: true
4904
4983
  });
4905
4984
  this._log(
4906
4985
  "DEBUG",
@@ -4982,12 +5061,14 @@ var RoutstrClient = class {
4982
5061
  const {
4983
5062
  token,
4984
5063
  baseUrl,
5064
+ mintUrl,
4985
5065
  initialTokenBalance,
4986
5066
  fallbackSatsSpent,
4987
5067
  response,
4988
5068
  modelId,
4989
5069
  usage,
4990
- requestId
5070
+ requestId,
5071
+ clientApiKey
4991
5072
  } = params;
4992
5073
  let satsSpent = initialTokenBalance;
4993
5074
  if (this.mode === "xcashu" && response) {
@@ -4995,19 +5076,14 @@ var RoutstrClient = class {
4995
5076
  if (refundToken) {
4996
5077
  try {
4997
5078
  const receiveResult = await this.cashuSpender.receiveToken(refundToken);
4998
- satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
5079
+ if (receiveResult.success) {
5080
+ this.storageAdapter.removeXcashuToken(baseUrl, token);
5081
+ satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
5082
+ }
4999
5083
  } catch (error) {
5000
5084
  this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
5001
5085
  }
5002
5086
  }
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
5087
  } else if (this.mode === "apikeys") {
5012
5088
  try {
5013
5089
  const latestBalanceInfo = await this.balanceManager.getTokenBalance(
@@ -5042,8 +5118,18 @@ var RoutstrClient = class {
5042
5118
  modelId,
5043
5119
  satsSpent,
5044
5120
  usage,
5045
- requestId
5121
+ requestId,
5122
+ clientApiKey
5046
5123
  });
5124
+ (async () => {
5125
+ try {
5126
+ const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
5127
+ this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
5128
+ const results = await this.cashuSpender.refundProviders(mintUrl);
5129
+ } catch (error) {
5130
+ this._log("ERROR", "Failed to refund providers:", error);
5131
+ }
5132
+ })();
5047
5133
  return satsSpent;
5048
5134
  }
5049
5135
  async _trackResponseUsage(params) {
@@ -5054,7 +5140,8 @@ var RoutstrClient = class {
5054
5140
  modelId,
5055
5141
  satsSpent,
5056
5142
  usage: providedUsage,
5057
- requestId: providedRequestId
5143
+ requestId: providedRequestId,
5144
+ clientApiKey
5058
5145
  } = params;
5059
5146
  if (!response || !modelId) {
5060
5147
  return;
@@ -5081,13 +5168,14 @@ var RoutstrClient = class {
5081
5168
  return;
5082
5169
  }
5083
5170
  const finalRequestId = requestId || "unknown";
5084
- const store = await getDefaultSdkStore();
5171
+ const store = this.sdkStore ?? await getDefaultSdkStore();
5085
5172
  const state = store.getState();
5173
+ const matchKey = clientApiKey ?? token;
5086
5174
  const matchingClient = state.clientIds.find(
5087
- (client) => client.apiKey === token
5175
+ (client) => client.apiKey === matchKey
5088
5176
  );
5089
5177
  const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
5090
- const usageTracking = getDefaultUsageTrackingDriver();
5178
+ const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
5091
5179
  const entry = {
5092
5180
  id: entryId,
5093
5181
  timestamp: Date.now(),
@@ -5162,11 +5250,11 @@ var RoutstrClient = class {
5162
5250
  return estimatedCosts;
5163
5251
  }
5164
5252
  /**
5165
- * Get pending cashu token amount
5253
+ * Get pending API key amount
5166
5254
  */
5167
5255
  _getPendingCashuTokenAmount() {
5168
- const distribution = this.storageAdapter.getCachedTokenDistribution();
5169
- return distribution.reduce((total, item) => total + item.amount, 0);
5256
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
5257
+ return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
5170
5258
  }
5171
5259
  /**
5172
5260
  * Handle errors and notify callbacks
@@ -5313,8 +5401,8 @@ var RoutstrClient = class {
5313
5401
  const spendResult = await this.cashuSpender.spend({
5314
5402
  mintUrl,
5315
5403
  amount,
5316
- baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
5317
- reuseToken: this.mode === "lazyrefund"
5404
+ baseUrl: "",
5405
+ reuseToken: false
5318
5406
  });
5319
5407
  if (!spendResult.token) {
5320
5408
  this._log(
@@ -5327,6 +5415,7 @@ var RoutstrClient = class {
5327
5415
  "DEBUG",
5328
5416
  `[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
5329
5417
  );
5418
+ this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
5330
5419
  }
5331
5420
  return {
5332
5421
  token: spendResult.token,
@@ -5364,6 +5453,7 @@ async function resolveRouteRequestContext(options) {
5364
5453
  modelId,
5365
5454
  requestBody,
5366
5455
  path = "/v1/chat/completions",
5456
+ headers = {},
5367
5457
  forcedProvider,
5368
5458
  walletAdapter,
5369
5459
  storageAdapter,
@@ -5374,7 +5464,9 @@ async function resolveRouteRequestContext(options) {
5374
5464
  forceRefresh = false,
5375
5465
  modelManager: providedModelManager,
5376
5466
  debugLevel,
5377
- mode = "apikeys"
5467
+ mode = "apikeys",
5468
+ usageTrackingDriver,
5469
+ sdkStore
5378
5470
  } = options;
5379
5471
  let modelManager;
5380
5472
  let providers;
@@ -5438,7 +5530,8 @@ async function resolveRouteRequestContext(options) {
5438
5530
  storageAdapter,
5439
5531
  providerRegistry,
5440
5532
  "min",
5441
- mode
5533
+ mode,
5534
+ { usageTrackingDriver, sdkStore }
5442
5535
  );
5443
5536
  if (debugLevel) {
5444
5537
  client.setDebugLevel(debugLevel);
@@ -5458,17 +5551,19 @@ async function resolveRouteRequestContext(options) {
5458
5551
  baseUrl,
5459
5552
  mintUrl,
5460
5553
  path,
5554
+ headers,
5461
5555
  modelId,
5462
5556
  proxiedBody
5463
5557
  };
5464
5558
  }
5465
5559
  async function routeRequests(options) {
5466
- const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5560
+ const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5467
5561
  try {
5468
5562
  const response = await client.routeRequest({
5469
5563
  path,
5470
5564
  method: "POST",
5471
5565
  body: proxiedBody,
5566
+ headers,
5472
5567
  baseUrl,
5473
5568
  mintUrl,
5474
5569
  modelId
@@ -5486,12 +5581,13 @@ async function routeRequests(options) {
5486
5581
  }
5487
5582
  async function routeRequestsToNodeResponse(options) {
5488
5583
  const { res } = options;
5489
- const { client, baseUrl, mintUrl, path, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5584
+ const { client, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
5490
5585
  try {
5491
5586
  await client.routeRequestToNodeResponse({
5492
5587
  path,
5493
5588
  method: "POST",
5494
5589
  body: proxiedBody,
5590
+ headers,
5495
5591
  baseUrl,
5496
5592
  mintUrl,
5497
5593
  modelId,
@@ -5539,6 +5635,8 @@ exports.SDK_STORAGE_KEYS = SDK_STORAGE_KEYS;
5539
5635
  exports.StreamProcessor = StreamProcessor;
5540
5636
  exports.StreamingError = StreamingError;
5541
5637
  exports.TokenOperationError = TokenOperationError;
5638
+ exports.createBunSqliteDriver = createBunSqliteDriver;
5639
+ exports.createBunSqliteUsageTrackingDriver = createBunSqliteUsageTrackingDriver;
5542
5640
  exports.createDiscoveryAdapterFromStore = createDiscoveryAdapterFromStore;
5543
5641
  exports.createIndexedDBDriver = createIndexedDBDriver;
5544
5642
  exports.createIndexedDBUsageTrackingDriver = createIndexedDBUsageTrackingDriver;
@@ -5564,5 +5662,6 @@ exports.localStorageDriver = localStorageDriver;
5564
5662
  exports.normalizeProviderUrl = normalizeProviderUrl;
5565
5663
  exports.routeRequests = routeRequests;
5566
5664
  exports.routeRequestsToNodeResponse = routeRequestsToNodeResponse;
5665
+ exports.setDefaultUsageTrackingDriver = setDefaultUsageTrackingDriver;
5567
5666
  //# sourceMappingURL=index.js.map
5568
5667
  //# sourceMappingURL=index.js.map