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