@routstr/sdk 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -13,9 +13,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
13
13
 
14
14
  // core/errors.ts
15
15
  var InsufficientBalanceError = class extends Error {
16
- constructor(required, available, maxMintBalance = 0, maxMintUrl = "") {
16
+ constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
17
17
  super(
18
- `Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
18
+ customMessage ?? `Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
19
19
  );
20
20
  this.required = required;
21
21
  this.available = available;
@@ -299,9 +299,10 @@ var ModelManager = class _ModelManager {
299
299
  * Uses cache if available and not expired
300
300
  * @param baseUrls List of provider base URLs to fetch from
301
301
  * @param forceRefresh Ignore cache and fetch fresh data
302
+ * @param onProgress Callback fired after each provider completes with current combined models
302
303
  * @returns Array of unique models with best prices selected
303
304
  */
304
- async fetchModels(baseUrls, forceRefresh = false) {
305
+ async fetchModels(baseUrls, forceRefresh = false, onProgress) {
305
306
  if (baseUrls.length === 0) {
306
307
  throw new NoProvidersAvailableError();
307
308
  }
@@ -311,6 +312,12 @@ var ModelManager = class _ModelManager {
311
312
  const estimateMinCost = (m) => {
312
313
  return m?.sats_pricing?.completion ?? 0;
313
314
  };
315
+ const emitProgress = () => {
316
+ if (onProgress) {
317
+ const currentModels = Array.from(bestById.values()).map((v) => v.model);
318
+ onProgress(currentModels);
319
+ }
320
+ };
314
321
  const fetchPromises = baseUrls.map(async (url) => {
315
322
  const base = url.endsWith("/") ? url : `${url}/`;
316
323
  try {
@@ -345,6 +352,7 @@ var ModelManager = class _ModelManager {
345
352
  }
346
353
  }
347
354
  }
355
+ emitProgress();
348
356
  return { success: true, base, list };
349
357
  } catch (error) {
350
358
  if (this.isProviderDownError(error)) {
@@ -766,6 +774,9 @@ var CashuSpender = class {
766
774
  return result;
767
775
  }
768
776
  async _getBalanceState() {
777
+ if (this.balanceManager) {
778
+ return this.balanceManager.getBalanceState();
779
+ }
769
780
  const mintBalances = await this.walletAdapter.getBalances();
770
781
  const units = this.walletAdapter.getMintUnits();
771
782
  let totalMintBalance = 0;
@@ -781,15 +792,16 @@ var CashuSpender = class {
781
792
  const providerBalances = {};
782
793
  let totalProviderBalance = 0;
783
794
  for (const pending of pendingDistribution) {
784
- providerBalances[pending.baseUrl] = pending.amount;
795
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
785
796
  totalProviderBalance += pending.amount;
786
797
  }
787
798
  const apiKeys = this.storageAdapter.getAllApiKeys();
788
799
  for (const apiKey of apiKeys) {
789
800
  if (!providerBalances[apiKey.baseUrl]) {
790
- providerBalances[apiKey.baseUrl] = apiKey.balance;
791
- totalProviderBalance += apiKey.balance;
801
+ providerBalances[apiKey.baseUrl] = 0;
792
802
  }
803
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
804
+ totalProviderBalance += apiKey.balance;
793
805
  }
794
806
  return {
795
807
  totalBalance: totalMintBalance + totalProviderBalance,
@@ -938,25 +950,12 @@ var CashuSpender = class {
938
950
  `[CashuSpender] _spendInternal: Could not reuse token, will create new token`
939
951
  );
940
952
  }
941
- const balances = await this.walletAdapter.getBalances();
942
- const units = this.walletAdapter.getMintUnits();
943
- let totalBalance = 0;
944
- for (const url in balances) {
945
- const balance = balances[url];
946
- const unit = units[url];
947
- const balanceInSats = getBalanceInSats(balance, unit);
948
- totalBalance += balanceInSats;
949
- }
950
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
951
- const totalPending = pendingDistribution.reduce(
952
- (sum, item) => sum + item.amount,
953
- 0
954
- );
953
+ const balanceState = await this._getBalanceState();
954
+ const totalAvailableBalance = balanceState.totalBalance;
955
955
  this._log(
956
956
  "DEBUG",
957
- `[CashuSpender] _spendInternal: totalBalance=${totalBalance}, totalPending=${totalPending}, adjustedAmount=${adjustedAmount}`
957
+ `[CashuSpender] _spendInternal: totalAvailableBalance=${totalAvailableBalance}, adjustedAmount=${adjustedAmount}`
958
958
  );
959
- const totalAvailableBalance = totalBalance + totalPending;
960
959
  if (totalAvailableBalance < adjustedAmount) {
961
960
  this._log(
962
961
  "ERROR",
@@ -964,8 +963,7 @@ var CashuSpender = class {
964
963
  );
965
964
  return this._createInsufficientBalanceError(
966
965
  adjustedAmount,
967
- balances,
968
- units,
966
+ balanceState.mintBalances,
969
967
  totalAvailableBalance
970
968
  );
971
969
  }
@@ -985,8 +983,7 @@ var CashuSpender = class {
985
983
  if ((tokenResult.error || "").includes("Insufficient balance")) {
986
984
  return this._createInsufficientBalanceError(
987
985
  adjustedAmount,
988
- balances,
989
- units,
986
+ balanceState.mintBalances,
990
987
  totalAvailableBalance
991
988
  );
992
989
  }
@@ -1031,6 +1028,7 @@ var CashuSpender = class {
1031
1028
  "DEBUG",
1032
1029
  `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
1033
1030
  );
1031
+ const units = this.walletAdapter.getMintUnits();
1034
1032
  return {
1035
1033
  token,
1036
1034
  status: "success",
@@ -1168,13 +1166,11 @@ var CashuSpender = class {
1168
1166
  /**
1169
1167
  * Create an insufficient balance error result
1170
1168
  */
1171
- _createInsufficientBalanceError(required, balances, units, availableBalance) {
1169
+ _createInsufficientBalanceError(required, normalizedBalances, availableBalance) {
1172
1170
  let maxBalance = 0;
1173
1171
  let maxMintUrl = "";
1174
- for (const mintUrl in balances) {
1175
- const balance = balances[mintUrl];
1176
- const unit = units[mintUrl];
1177
- const balanceInSats = getBalanceInSats(balance, unit);
1172
+ for (const mintUrl in normalizedBalances) {
1173
+ const balanceInSats = normalizedBalances[mintUrl];
1178
1174
  if (balanceInSats > maxBalance) {
1179
1175
  maxBalance = balanceInSats;
1180
1176
  maxMintUrl = mintUrl;
@@ -1235,6 +1231,39 @@ var BalanceManager = class {
1235
1231
  }
1236
1232
  }
1237
1233
  cashuSpender;
1234
+ async getBalanceState() {
1235
+ const mintBalances = await this.walletAdapter.getBalances();
1236
+ const units = this.walletAdapter.getMintUnits();
1237
+ let totalMintBalance = 0;
1238
+ const normalizedMintBalances = {};
1239
+ for (const url in mintBalances) {
1240
+ const balance = mintBalances[url];
1241
+ const unit = units[url];
1242
+ const balanceInSats = getBalanceInSats(balance, unit);
1243
+ normalizedMintBalances[url] = balanceInSats;
1244
+ totalMintBalance += balanceInSats;
1245
+ }
1246
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1247
+ const providerBalances = {};
1248
+ let totalProviderBalance = 0;
1249
+ for (const pending of pendingDistribution) {
1250
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
1251
+ totalProviderBalance += pending.amount;
1252
+ }
1253
+ const apiKeys = this.storageAdapter.getAllApiKeys();
1254
+ for (const apiKey of apiKeys) {
1255
+ if (!providerBalances[apiKey.baseUrl]) {
1256
+ providerBalances[apiKey.baseUrl] = 0;
1257
+ }
1258
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
1259
+ totalProviderBalance += apiKey.balance;
1260
+ }
1261
+ return {
1262
+ totalBalance: totalMintBalance + totalProviderBalance,
1263
+ providerBalances,
1264
+ mintBalances: normalizedMintBalances
1265
+ };
1266
+ }
1238
1267
  /**
1239
1268
  * Unified refund - handles both NIP-60 and legacy wallet refunds
1240
1269
  */
@@ -1445,6 +1474,10 @@ var BalanceManager = class {
1445
1474
  requestId
1446
1475
  };
1447
1476
  } catch (error) {
1477
+ console.log(
1478
+ "DEBUG",
1479
+ `[TopuPU] topup: Topup result for ${baseUrl}: error=${error}`
1480
+ );
1448
1481
  if (cashuToken) {
1449
1482
  await this._recoverFailedTopUp(cashuToken);
1450
1483
  }
@@ -1464,23 +1497,42 @@ var BalanceManager = class {
1464
1497
  if (!adjustedAmount || isNaN(adjustedAmount)) {
1465
1498
  return { success: false, error: "Invalid top up amount" };
1466
1499
  }
1500
+ const balanceState = await this.getBalanceState();
1467
1501
  const balances = await this.walletAdapter.getBalances();
1468
1502
  const units = this.walletAdapter.getMintUnits();
1469
- let totalMintBalance = 0;
1470
- for (const url in balances) {
1471
- const unit = units[url];
1472
- const balanceInSats = getBalanceInSats(balances[url], unit);
1473
- totalMintBalance += balanceInSats;
1474
- }
1475
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1476
- const refundablePending = pendingDistribution.filter((entry) => entry.baseUrl !== baseUrl).reduce((sum, entry) => sum + entry.amount, 0);
1477
- if (totalMintBalance < adjustedAmount && totalMintBalance + refundablePending >= adjustedAmount && retryCount < 1) {
1503
+ const totalMintBalance = Object.values(balanceState.mintBalances).reduce(
1504
+ (sum, value) => sum + value,
1505
+ 0
1506
+ );
1507
+ const targetProviderBalance = balanceState.providerBalances[baseUrl] || 0;
1508
+ const refundableProviderBalance = Object.entries(
1509
+ balanceState.providerBalances
1510
+ ).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
1511
+ if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 1) {
1478
1512
  await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
1479
1513
  return this.createProviderToken({
1480
1514
  ...options,
1481
1515
  retryCount: retryCount + 1
1482
1516
  });
1483
1517
  }
1518
+ if (totalMintBalance + targetProviderBalance < adjustedAmount) {
1519
+ const error = new InsufficientBalanceError(
1520
+ adjustedAmount,
1521
+ totalMintBalance + targetProviderBalance,
1522
+ totalMintBalance,
1523
+ Object.entries(balanceState.mintBalances).reduce(
1524
+ (max, [url, balance]) => balance > max.balance ? { url, balance } : max,
1525
+ { url: "", balance: 0 }
1526
+ ).url
1527
+ );
1528
+ return { success: false, error: error.message };
1529
+ }
1530
+ if (targetProviderBalance >= adjustedAmount) {
1531
+ return {
1532
+ success: true,
1533
+ amountSpent: 0
1534
+ };
1535
+ }
1484
1536
  const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
1485
1537
  let requiredAmount = adjustedAmount;
1486
1538
  const supportedMintsOnly = providerMints.length > 0;
@@ -1596,10 +1648,14 @@ var BalanceManager = class {
1596
1648
  }
1597
1649
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
1598
1650
  const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1651
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1599
1652
  const toRefund = pendingDistribution.filter(
1600
1653
  (pending) => pending.baseUrl !== baseUrl
1601
1654
  );
1602
- const refundResults = await Promise.allSettled(
1655
+ const apiKeysToRefund = apiKeyDistribution.filter(
1656
+ (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1657
+ );
1658
+ const tokenRefundResults = await Promise.allSettled(
1603
1659
  toRefund.map(async (pending) => {
1604
1660
  const token = this.storageAdapter.getToken(pending.baseUrl);
1605
1661
  if (!token) {
@@ -1617,11 +1673,32 @@ var BalanceManager = class {
1617
1673
  return { baseUrl: pending.baseUrl, success: result.success };
1618
1674
  })
1619
1675
  );
1620
- for (const result of refundResults) {
1676
+ for (const result of tokenRefundResults) {
1621
1677
  if (result.status === "fulfilled" && result.value.success) {
1622
1678
  this.storageAdapter.removeToken(result.value.baseUrl);
1623
1679
  }
1624
1680
  }
1681
+ const apiKeyRefundResults = await Promise.allSettled(
1682
+ apiKeysToRefund.map(async (apiKeyEntry) => {
1683
+ const fullApiKeyEntry = this.storageAdapter.getApiKey(
1684
+ apiKeyEntry.baseUrl
1685
+ );
1686
+ if (!fullApiKeyEntry) {
1687
+ return { baseUrl: apiKeyEntry.baseUrl, success: false };
1688
+ }
1689
+ const result = await this.refundApiKey({
1690
+ mintUrl,
1691
+ baseUrl: apiKeyEntry.baseUrl,
1692
+ apiKey: fullApiKeyEntry.key
1693
+ });
1694
+ return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
1695
+ })
1696
+ );
1697
+ for (const result of apiKeyRefundResults) {
1698
+ if (result.status === "fulfilled" && result.value.success) {
1699
+ this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
1700
+ }
1701
+ }
1625
1702
  }
1626
1703
  /**
1627
1704
  * Fetch refund token from provider API
@@ -2872,10 +2949,34 @@ var RoutstrClient = class {
2872
2949
  }
2873
2950
  }
2874
2951
  if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
2952
+ this.storageAdapter.getApiKey(baseUrl);
2953
+ let topupAmount = params.requiredSats;
2954
+ try {
2955
+ let currentBalance = 0;
2956
+ if (this.mode === "apikeys") {
2957
+ const currentBalanceInfo = await this.balanceManager.getTokenBalance(
2958
+ params.token,
2959
+ baseUrl
2960
+ );
2961
+ currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
2962
+ } else if (this.mode === "lazyrefund") {
2963
+ const distribution = this.storageAdapter.getCachedTokenDistribution();
2964
+ const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
2965
+ currentBalance = tokenEntry?.amount ?? 0;
2966
+ }
2967
+ const shortfall = Math.max(0, params.requiredSats - currentBalance);
2968
+ topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
2969
+ } catch (e) {
2970
+ this._log(
2971
+ "WARN",
2972
+ "Could not get current token balance for topup calculation:",
2973
+ e
2974
+ );
2975
+ }
2875
2976
  const topupResult = await this.balanceManager.topUp({
2876
2977
  mintUrl,
2877
2978
  baseUrl,
2878
- amount: params.requiredSats * TOPUP_MARGIN,
2979
+ amount: topupAmount * TOPUP_MARGIN,
2879
2980
  token: params.token
2880
2981
  });
2881
2982
  this._log(
@@ -2893,7 +2994,7 @@ var RoutstrClient = class {
2893
2994
  "DEBUG",
2894
2995
  `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
2895
2996
  );
2896
- throw new InsufficientBalanceError(required, available);
2997
+ throw new InsufficientBalanceError(required, available, 0, "", message);
2897
2998
  } else {
2898
2999
  this._log(
2899
3000
  "DEBUG",
@@ -2956,7 +3057,10 @@ var RoutstrClient = class {
2956
3057
  retryToken = latestBalanceInfo.apiKey;
2957
3058
  }
2958
3059
  if (latestTokenBalance >= 0) {
2959
- this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
3060
+ this.storageAdapter.updateApiKeyBalance(
3061
+ baseUrl,
3062
+ latestTokenBalance
3063
+ );
2960
3064
  }
2961
3065
  }
2962
3066
  } catch (error) {
@@ -3626,7 +3730,11 @@ var createSqliteDriver = (options = {}) => {
3626
3730
  };
3627
3731
 
3628
3732
  // storage/drivers/indexedDB.ts
3733
+ var isBrowser = typeof indexedDB !== "undefined";
3629
3734
  var openDatabase = (dbName, storeName) => {
3735
+ if (!isBrowser) {
3736
+ return Promise.reject(new Error("IndexedDB is not available"));
3737
+ }
3630
3738
  return new Promise((resolve, reject) => {
3631
3739
  const request = indexedDB.open(dbName, 1);
3632
3740
  request.onupgradeneeded = () => {
@@ -3730,7 +3838,9 @@ var SDK_STORAGE_KEYS = {
3730
3838
  CHILD_KEYS: "child_keys",
3731
3839
  ROUTSTR21_MODELS: "routstr21Models",
3732
3840
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
3733
- CACHED_RECEIVE_TOKENS: "cached_receive_tokens"
3841
+ CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
3842
+ USAGE_TRACKING: "usage_tracking",
3843
+ CLIENT_IDS: "client_ids"
3734
3844
  };
3735
3845
 
3736
3846
  // storage/store.ts
@@ -3748,9 +3858,159 @@ var getCashuTokenBalance = (token) => {
3748
3858
  return 0;
3749
3859
  }
3750
3860
  };
3751
- var createSdkStore = async ({
3752
- driver
3753
- }) => {
3861
+ var createEmptyStore = (driver) => createStore((set, get) => ({
3862
+ modelsFromAllProviders: {},
3863
+ lastUsedModel: null,
3864
+ baseUrlsList: [],
3865
+ lastBaseUrlsUpdate: null,
3866
+ disabledProviders: [],
3867
+ mintsFromAllProviders: {},
3868
+ infoFromAllProviders: {},
3869
+ lastModelsUpdate: {},
3870
+ cachedTokens: [],
3871
+ apiKeys: [],
3872
+ childKeys: [],
3873
+ routstr21Models: [],
3874
+ lastRoutstr21ModelsUpdate: null,
3875
+ cachedReceiveTokens: [],
3876
+ usageTracking: [],
3877
+ clientIds: [],
3878
+ setModelsFromAllProviders: (value) => {
3879
+ const normalized = {};
3880
+ for (const [baseUrl, models] of Object.entries(value)) {
3881
+ normalized[normalizeBaseUrl(baseUrl)] = models;
3882
+ }
3883
+ void driver.setItem(
3884
+ SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
3885
+ normalized
3886
+ );
3887
+ set({ modelsFromAllProviders: normalized });
3888
+ },
3889
+ setLastUsedModel: (value) => {
3890
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
3891
+ set({ lastUsedModel: value });
3892
+ },
3893
+ setBaseUrlsList: (value) => {
3894
+ const normalized = value.map((url) => normalizeBaseUrl(url));
3895
+ void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
3896
+ set({ baseUrlsList: normalized });
3897
+ },
3898
+ setBaseUrlsLastUpdate: (value) => {
3899
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
3900
+ set({ lastBaseUrlsUpdate: value });
3901
+ },
3902
+ setDisabledProviders: (value) => {
3903
+ const normalized = value.map((url) => normalizeBaseUrl(url));
3904
+ void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
3905
+ set({ disabledProviders: normalized });
3906
+ },
3907
+ setMintsFromAllProviders: (value) => {
3908
+ const normalized = {};
3909
+ for (const [baseUrl, mints] of Object.entries(value)) {
3910
+ normalized[normalizeBaseUrl(baseUrl)] = mints.map(
3911
+ (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
3912
+ );
3913
+ }
3914
+ void driver.setItem(
3915
+ SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
3916
+ normalized
3917
+ );
3918
+ set({ mintsFromAllProviders: normalized });
3919
+ },
3920
+ setInfoFromAllProviders: (value) => {
3921
+ const normalized = {};
3922
+ for (const [baseUrl, info] of Object.entries(value)) {
3923
+ normalized[normalizeBaseUrl(baseUrl)] = info;
3924
+ }
3925
+ void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
3926
+ set({ infoFromAllProviders: normalized });
3927
+ },
3928
+ setLastModelsUpdate: (value) => {
3929
+ const normalized = {};
3930
+ for (const [baseUrl, timestamp] of Object.entries(value)) {
3931
+ normalized[normalizeBaseUrl(baseUrl)] = timestamp;
3932
+ }
3933
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
3934
+ set({ lastModelsUpdate: normalized });
3935
+ },
3936
+ setCachedTokens: (value) => {
3937
+ set((state) => {
3938
+ const updates = typeof value === "function" ? value(state.cachedTokens) : value;
3939
+ const normalized = updates.map((entry) => ({
3940
+ ...entry,
3941
+ baseUrl: normalizeBaseUrl(entry.baseUrl),
3942
+ balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
3943
+ lastUsed: entry.lastUsed ?? null
3944
+ }));
3945
+ void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
3946
+ return { cachedTokens: normalized };
3947
+ });
3948
+ },
3949
+ setApiKeys: (value) => {
3950
+ set((state) => {
3951
+ const updates = typeof value === "function" ? value(state.apiKeys) : value;
3952
+ const normalized = updates.map((entry) => ({
3953
+ ...entry,
3954
+ baseUrl: normalizeBaseUrl(entry.baseUrl),
3955
+ balance: entry.balance ?? 0,
3956
+ lastUsed: entry.lastUsed ?? null
3957
+ }));
3958
+ void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
3959
+ return { apiKeys: normalized };
3960
+ });
3961
+ },
3962
+ setChildKeys: (value) => {
3963
+ set((state) => {
3964
+ const updates = typeof value === "function" ? value(state.childKeys) : value;
3965
+ const normalized = updates.map((entry) => ({
3966
+ parentBaseUrl: normalizeBaseUrl(entry.parentBaseUrl),
3967
+ childKey: entry.childKey,
3968
+ balance: entry.balance ?? 0,
3969
+ balanceLimit: entry.balanceLimit,
3970
+ validityDate: entry.validityDate,
3971
+ createdAt: entry.createdAt ?? Date.now()
3972
+ }));
3973
+ void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
3974
+ return { childKeys: normalized };
3975
+ });
3976
+ },
3977
+ setRoutstr21Models: (value) => {
3978
+ void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
3979
+ set({ routstr21Models: value });
3980
+ },
3981
+ setRoutstr21ModelsLastUpdate: (value) => {
3982
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE, value);
3983
+ set({ lastRoutstr21ModelsUpdate: value });
3984
+ },
3985
+ setCachedReceiveTokens: (value) => {
3986
+ const normalized = value.map((entry) => ({
3987
+ token: entry.token,
3988
+ amount: entry.amount,
3989
+ unit: entry.unit || "sat",
3990
+ createdAt: entry.createdAt ?? Date.now()
3991
+ }));
3992
+ void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
3993
+ set({ cachedReceiveTokens: normalized });
3994
+ },
3995
+ setUsageTracking: (value) => {
3996
+ void driver.setItem(SDK_STORAGE_KEYS.USAGE_TRACKING, value);
3997
+ set({ usageTracking: value });
3998
+ },
3999
+ setClientIds: (value) => {
4000
+ set((state) => {
4001
+ const updates = typeof value === "function" ? value(state.clientIds) : value;
4002
+ const normalized = updates.map((entry) => ({
4003
+ ...entry,
4004
+ baseUrl: normalizeBaseUrl(entry.baseUrl),
4005
+ createdAt: entry.createdAt ?? Date.now(),
4006
+ lastUsed: entry.lastUsed ?? null
4007
+ }));
4008
+ void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
4009
+ return { clientIds: normalized };
4010
+ });
4011
+ }
4012
+ }));
4013
+ var hydrateStoreFromDriver = async (store, driver) => {
3754
4014
  const [
3755
4015
  rawModels,
3756
4016
  lastUsedModel,
@@ -3765,7 +4025,9 @@ var createSdkStore = async ({
3765
4025
  rawChildKeys,
3766
4026
  rawRoutstr21Models,
3767
4027
  rawLastRoutstr21ModelsUpdate,
3768
- rawCachedReceiveTokens
4028
+ rawCachedReceiveTokens,
4029
+ rawUsageTracking,
4030
+ rawClientIds
3769
4031
  ] = await Promise.all([
3770
4032
  driver.getItem(
3771
4033
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3795,7 +4057,9 @@ var createSdkStore = async ({
3795
4057
  SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
3796
4058
  null
3797
4059
  ),
3798
- driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, [])
4060
+ driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
4061
+ driver.getItem(SDK_STORAGE_KEYS.USAGE_TRACKING, []),
4062
+ driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
3799
4063
  ]);
3800
4064
  const modelsFromAllProviders = Object.fromEntries(
3801
4065
  Object.entries(rawModels).map(([baseUrl, models]) => [
@@ -3853,7 +4117,14 @@ var createSdkStore = async ({
3853
4117
  unit: entry.unit || "sat",
3854
4118
  createdAt: entry.createdAt ?? Date.now()
3855
4119
  }));
3856
- return createStore((set, get) => ({
4120
+ const usageTracking = rawUsageTracking;
4121
+ const clientIds = rawClientIds.map((entry) => ({
4122
+ ...entry,
4123
+ baseUrl: normalizeBaseUrl(entry.baseUrl),
4124
+ createdAt: entry.createdAt ?? Date.now(),
4125
+ lastUsed: entry.lastUsed ?? null
4126
+ }));
4127
+ store.setState({
3857
4128
  modelsFromAllProviders,
3858
4129
  lastUsedModel,
3859
4130
  baseUrlsList,
@@ -3868,124 +4139,18 @@ var createSdkStore = async ({
3868
4139
  routstr21Models,
3869
4140
  lastRoutstr21ModelsUpdate,
3870
4141
  cachedReceiveTokens,
3871
- setModelsFromAllProviders: (value) => {
3872
- const normalized = {};
3873
- for (const [baseUrl, models] of Object.entries(value)) {
3874
- normalized[normalizeBaseUrl(baseUrl)] = models;
3875
- }
3876
- void driver.setItem(
3877
- SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
3878
- normalized
3879
- );
3880
- set({ modelsFromAllProviders: normalized });
3881
- },
3882
- setLastUsedModel: (value) => {
3883
- void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
3884
- set({ lastUsedModel: value });
3885
- },
3886
- setBaseUrlsList: (value) => {
3887
- const normalized = value.map((url) => normalizeBaseUrl(url));
3888
- void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
3889
- set({ baseUrlsList: normalized });
3890
- },
3891
- setBaseUrlsLastUpdate: (value) => {
3892
- void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
3893
- set({ lastBaseUrlsUpdate: value });
3894
- },
3895
- setDisabledProviders: (value) => {
3896
- const normalized = value.map((url) => normalizeBaseUrl(url));
3897
- void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
3898
- set({ disabledProviders: normalized });
3899
- },
3900
- setMintsFromAllProviders: (value) => {
3901
- const normalized = {};
3902
- for (const [baseUrl, mints] of Object.entries(value)) {
3903
- normalized[normalizeBaseUrl(baseUrl)] = mints.map(
3904
- (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
3905
- );
3906
- }
3907
- void driver.setItem(
3908
- SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
3909
- normalized
3910
- );
3911
- set({ mintsFromAllProviders: normalized });
3912
- },
3913
- setInfoFromAllProviders: (value) => {
3914
- const normalized = {};
3915
- for (const [baseUrl, info] of Object.entries(value)) {
3916
- normalized[normalizeBaseUrl(baseUrl)] = info;
3917
- }
3918
- void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
3919
- set({ infoFromAllProviders: normalized });
3920
- },
3921
- setLastModelsUpdate: (value) => {
3922
- const normalized = {};
3923
- for (const [baseUrl, timestamp] of Object.entries(value)) {
3924
- normalized[normalizeBaseUrl(baseUrl)] = timestamp;
3925
- }
3926
- void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
3927
- set({ lastModelsUpdate: normalized });
3928
- },
3929
- setCachedTokens: (value) => {
3930
- set((state) => {
3931
- const updates = typeof value === "function" ? value(state.cachedTokens) : value;
3932
- const normalized = updates.map((entry) => ({
3933
- ...entry,
3934
- baseUrl: normalizeBaseUrl(entry.baseUrl),
3935
- balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
3936
- lastUsed: entry.lastUsed ?? null
3937
- }));
3938
- void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
3939
- return { cachedTokens: normalized };
3940
- });
3941
- },
3942
- setApiKeys: (value) => {
3943
- set((state) => {
3944
- const updates = typeof value === "function" ? value(state.apiKeys) : value;
3945
- const normalized = updates.map((entry) => ({
3946
- ...entry,
3947
- baseUrl: normalizeBaseUrl(entry.baseUrl),
3948
- balance: entry.balance ?? 0,
3949
- lastUsed: entry.lastUsed ?? null
3950
- }));
3951
- void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
3952
- return { apiKeys: normalized };
3953
- });
3954
- },
3955
- setChildKeys: (value) => {
3956
- set((state) => {
3957
- const updates = typeof value === "function" ? value(state.childKeys) : value;
3958
- const normalized = updates.map((entry) => ({
3959
- parentBaseUrl: normalizeBaseUrl(entry.parentBaseUrl),
3960
- childKey: entry.childKey,
3961
- balance: entry.balance ?? 0,
3962
- balanceLimit: entry.balanceLimit,
3963
- validityDate: entry.validityDate,
3964
- createdAt: entry.createdAt ?? Date.now()
3965
- }));
3966
- void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
3967
- return { childKeys: normalized };
3968
- });
3969
- },
3970
- setRoutstr21Models: (value) => {
3971
- void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
3972
- set({ routstr21Models: value });
3973
- },
3974
- setRoutstr21ModelsLastUpdate: (value) => {
3975
- void driver.setItem(SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE, value);
3976
- set({ lastRoutstr21ModelsUpdate: value });
3977
- },
3978
- setCachedReceiveTokens: (value) => {
3979
- const normalized = value.map((entry) => ({
3980
- token: entry.token,
3981
- amount: entry.amount,
3982
- unit: entry.unit || "sat",
3983
- createdAt: entry.createdAt ?? Date.now()
3984
- }));
3985
- void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
3986
- set({ cachedReceiveTokens: normalized });
3987
- }
3988
- }));
4142
+ usageTracking,
4143
+ clientIds
4144
+ });
4145
+ };
4146
+ var createSdkStore = ({
4147
+ driver
4148
+ }) => {
4149
+ const store = createEmptyStore(driver);
4150
+ return {
4151
+ store,
4152
+ hydrate: hydrateStoreFromDriver(store, driver)
4153
+ };
3989
4154
  };
3990
4155
  var createDiscoveryAdapterFromStore = (store) => ({
3991
4156
  getCachedModels: () => store.getState().modelsFromAllProviders,
@@ -4250,7 +4415,7 @@ var createProviderRegistryFromStore = (store) => ({
4250
4415
  });
4251
4416
 
4252
4417
  // storage/index.ts
4253
- var isBrowser = () => {
4418
+ var isBrowser2 = () => {
4254
4419
  try {
4255
4420
  return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
4256
4421
  } catch {
@@ -4270,7 +4435,7 @@ var isBun2 = () => {
4270
4435
  };
4271
4436
  var getDefaultSdkDriver = () => {
4272
4437
  if (defaultDriver) return defaultDriver;
4273
- if (isBrowser()) {
4438
+ if (isBrowser2()) {
4274
4439
  defaultDriver = localStorageDriver;
4275
4440
  return defaultDriver;
4276
4441
  }
@@ -4285,12 +4450,12 @@ var getDefaultSdkDriver = () => {
4285
4450
  defaultDriver = createMemoryDriver();
4286
4451
  return defaultDriver;
4287
4452
  };
4288
- var defaultStorePromise = null;
4453
+ var defaultStore = null;
4289
4454
  var getDefaultSdkStore = () => {
4290
- if (!defaultStorePromise) {
4291
- defaultStorePromise = createSdkStore({ driver: getDefaultSdkDriver() });
4455
+ if (!defaultStore) {
4456
+ defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
4292
4457
  }
4293
- return defaultStorePromise;
4458
+ return defaultStore.hydrate.then(() => defaultStore.store);
4294
4459
  };
4295
4460
  var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
4296
4461
  var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
@@ -4311,7 +4476,8 @@ async function routeRequests(options) {
4311
4476
  torMode = false,
4312
4477
  forceRefresh = false,
4313
4478
  modelManager: providedModelManager,
4314
- debugLevel
4479
+ debugLevel,
4480
+ mode = "apikeys"
4315
4481
  } = options;
4316
4482
  let modelManager;
4317
4483
  let providers;
@@ -4376,7 +4542,7 @@ async function routeRequests(options) {
4376
4542
  storageAdapter,
4377
4543
  providerRegistry,
4378
4544
  alertLevel,
4379
- "apikeys"
4545
+ mode
4380
4546
  );
4381
4547
  if (debugLevel) {
4382
4548
  client.setDebugLevel(debugLevel);