@routstr/sdk 0.1.7 → 0.1.8

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.d.mts CHANGED
@@ -5,7 +5,7 @@ import { ModelManager } from './discovery/index.mjs';
5
5
  export { MintDiscovery, ModelManagerConfig } from './discovery/index.mjs';
6
6
  import { W as WalletAdapter, S as StorageAdapter, P as ProviderRegistry } from './interfaces-B85Wx7ni.mjs';
7
7
  export { A as ApiKeyEntry, C as ChildKeyEntry, R as RoutstrClientOptions, a as StreamingCallbacks } from './interfaces-B85Wx7ni.mjs';
8
- export { BalanceManager, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, RefundOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
8
+ export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, RefundOptions, SpendOptions, TopUpOptions } from './wallet/index.mjs';
9
9
  import { DebugLevel } from './client/index.mjs';
10
10
  export { AlertLevel, FetchOptions, ModelProviderPrice, ProviderManager, RouteRequestParams, RoutstrClient, RoutstrClientMode, StreamCallbacks, StreamProcessor } from './client/index.mjs';
11
11
  export { SDK_STORAGE_KEYS, SdkStore, StorageDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createMemoryDriver, createProviderRegistryFromStore, createSdkStore, createSqliteDriver, createStorageAdapterFromStore, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, localStorageDriver } from './storage/index.mjs';
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ModelManager } from './discovery/index.js';
5
5
  export { MintDiscovery, ModelManagerConfig } from './discovery/index.js';
6
6
  import { W as WalletAdapter, S as StorageAdapter, P as ProviderRegistry } from './interfaces-BVNyAmKu.js';
7
7
  export { A as ApiKeyEntry, C as ChildKeyEntry, R as RoutstrClientOptions, a as StreamingCallbacks } from './interfaces-BVNyAmKu.js';
8
- export { BalanceManager, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, RefundOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
8
+ export { BalanceManager, BalanceState, CashuSpender, CreateProviderTokenOptions, ProviderTokenResult, RefundApiKeyOptions, RefundOptions, SpendOptions, TopUpOptions } from './wallet/index.js';
9
9
  import { DebugLevel } from './client/index.js';
10
10
  export { AlertLevel, FetchOptions, ModelProviderPrice, ProviderManager, RouteRequestParams, RoutstrClient, RoutstrClientMode, StreamCallbacks, StreamProcessor } from './client/index.js';
11
11
  export { SDK_STORAGE_KEYS, SdkStore, StorageDriver, createDiscoveryAdapterFromStore, createIndexedDBDriver, createMemoryDriver, createProviderRegistryFromStore, createSdkStore, createSqliteDriver, createStorageAdapterFromStore, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, localStorageDriver } from './storage/index.js';
package/dist/index.js CHANGED
@@ -768,6 +768,9 @@ var CashuSpender = class {
768
768
  return result;
769
769
  }
770
770
  async _getBalanceState() {
771
+ if (this.balanceManager) {
772
+ return this.balanceManager.getBalanceState();
773
+ }
771
774
  const mintBalances = await this.walletAdapter.getBalances();
772
775
  const units = this.walletAdapter.getMintUnits();
773
776
  let totalMintBalance = 0;
@@ -783,15 +786,16 @@ var CashuSpender = class {
783
786
  const providerBalances = {};
784
787
  let totalProviderBalance = 0;
785
788
  for (const pending of pendingDistribution) {
786
- providerBalances[pending.baseUrl] = pending.amount;
789
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
787
790
  totalProviderBalance += pending.amount;
788
791
  }
789
792
  const apiKeys = this.storageAdapter.getAllApiKeys();
790
793
  for (const apiKey of apiKeys) {
791
794
  if (!providerBalances[apiKey.baseUrl]) {
792
- providerBalances[apiKey.baseUrl] = apiKey.balance;
793
- totalProviderBalance += apiKey.balance;
795
+ providerBalances[apiKey.baseUrl] = 0;
794
796
  }
797
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
798
+ totalProviderBalance += apiKey.balance;
795
799
  }
796
800
  return {
797
801
  totalBalance: totalMintBalance + totalProviderBalance,
@@ -940,25 +944,12 @@ var CashuSpender = class {
940
944
  `[CashuSpender] _spendInternal: Could not reuse token, will create new token`
941
945
  );
942
946
  }
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
- );
947
+ const balanceState = await this._getBalanceState();
948
+ const totalAvailableBalance = balanceState.totalBalance;
957
949
  this._log(
958
950
  "DEBUG",
959
- `[CashuSpender] _spendInternal: totalBalance=${totalBalance}, totalPending=${totalPending}, adjustedAmount=${adjustedAmount}`
951
+ `[CashuSpender] _spendInternal: totalAvailableBalance=${totalAvailableBalance}, adjustedAmount=${adjustedAmount}`
960
952
  );
961
- const totalAvailableBalance = totalBalance + totalPending;
962
953
  if (totalAvailableBalance < adjustedAmount) {
963
954
  this._log(
964
955
  "ERROR",
@@ -966,8 +957,7 @@ var CashuSpender = class {
966
957
  );
967
958
  return this._createInsufficientBalanceError(
968
959
  adjustedAmount,
969
- balances,
970
- units,
960
+ balanceState.mintBalances,
971
961
  totalAvailableBalance
972
962
  );
973
963
  }
@@ -987,8 +977,7 @@ var CashuSpender = class {
987
977
  if ((tokenResult.error || "").includes("Insufficient balance")) {
988
978
  return this._createInsufficientBalanceError(
989
979
  adjustedAmount,
990
- balances,
991
- units,
980
+ balanceState.mintBalances,
992
981
  totalAvailableBalance
993
982
  );
994
983
  }
@@ -1033,6 +1022,7 @@ var CashuSpender = class {
1033
1022
  "DEBUG",
1034
1023
  `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
1035
1024
  );
1025
+ const units = this.walletAdapter.getMintUnits();
1036
1026
  return {
1037
1027
  token,
1038
1028
  status: "success",
@@ -1170,13 +1160,11 @@ var CashuSpender = class {
1170
1160
  /**
1171
1161
  * Create an insufficient balance error result
1172
1162
  */
1173
- _createInsufficientBalanceError(required, balances, units, availableBalance) {
1163
+ _createInsufficientBalanceError(required, normalizedBalances, availableBalance) {
1174
1164
  let maxBalance = 0;
1175
1165
  let maxMintUrl = "";
1176
- for (const mintUrl in balances) {
1177
- const balance = balances[mintUrl];
1178
- const unit = units[mintUrl];
1179
- const balanceInSats = getBalanceInSats(balance, unit);
1166
+ for (const mintUrl in normalizedBalances) {
1167
+ const balanceInSats = normalizedBalances[mintUrl];
1180
1168
  if (balanceInSats > maxBalance) {
1181
1169
  maxBalance = balanceInSats;
1182
1170
  maxMintUrl = mintUrl;
@@ -1237,6 +1225,39 @@ var BalanceManager = class {
1237
1225
  }
1238
1226
  }
1239
1227
  cashuSpender;
1228
+ async getBalanceState() {
1229
+ const mintBalances = await this.walletAdapter.getBalances();
1230
+ const units = this.walletAdapter.getMintUnits();
1231
+ let totalMintBalance = 0;
1232
+ const normalizedMintBalances = {};
1233
+ for (const url in mintBalances) {
1234
+ const balance = mintBalances[url];
1235
+ const unit = units[url];
1236
+ const balanceInSats = getBalanceInSats(balance, unit);
1237
+ normalizedMintBalances[url] = balanceInSats;
1238
+ totalMintBalance += balanceInSats;
1239
+ }
1240
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1241
+ const providerBalances = {};
1242
+ let totalProviderBalance = 0;
1243
+ for (const pending of pendingDistribution) {
1244
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
1245
+ totalProviderBalance += pending.amount;
1246
+ }
1247
+ const apiKeys = this.storageAdapter.getAllApiKeys();
1248
+ for (const apiKey of apiKeys) {
1249
+ if (!providerBalances[apiKey.baseUrl]) {
1250
+ providerBalances[apiKey.baseUrl] = 0;
1251
+ }
1252
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
1253
+ totalProviderBalance += apiKey.balance;
1254
+ }
1255
+ return {
1256
+ totalBalance: totalMintBalance + totalProviderBalance,
1257
+ providerBalances,
1258
+ mintBalances: normalizedMintBalances
1259
+ };
1260
+ }
1240
1261
  /**
1241
1262
  * Unified refund - handles both NIP-60 and legacy wallet refunds
1242
1263
  */
@@ -1466,17 +1487,17 @@ var BalanceManager = class {
1466
1487
  if (!adjustedAmount || isNaN(adjustedAmount)) {
1467
1488
  return { success: false, error: "Invalid top up amount" };
1468
1489
  }
1490
+ const balanceState = await this.getBalanceState();
1469
1491
  const balances = await this.walletAdapter.getBalances();
1470
1492
  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) {
1493
+ const totalMintBalance = Object.values(balanceState.mintBalances).reduce(
1494
+ (sum, value) => sum + value,
1495
+ 0
1496
+ );
1497
+ const refundableProviderBalance = Object.entries(
1498
+ balanceState.providerBalances
1499
+ ).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
1500
+ if (totalMintBalance < adjustedAmount && totalMintBalance + refundableProviderBalance >= adjustedAmount && retryCount < 1) {
1480
1501
  await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
1481
1502
  return this.createProviderToken({
1482
1503
  ...options,
@@ -1598,10 +1619,14 @@ var BalanceManager = class {
1598
1619
  }
1599
1620
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
1600
1621
  const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1622
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1601
1623
  const toRefund = pendingDistribution.filter(
1602
1624
  (pending) => pending.baseUrl !== baseUrl
1603
1625
  );
1604
- const refundResults = await Promise.allSettled(
1626
+ const apiKeysToRefund = apiKeyDistribution.filter(
1627
+ (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1628
+ );
1629
+ const tokenRefundResults = await Promise.allSettled(
1605
1630
  toRefund.map(async (pending) => {
1606
1631
  const token = this.storageAdapter.getToken(pending.baseUrl);
1607
1632
  if (!token) {
@@ -1619,11 +1644,32 @@ var BalanceManager = class {
1619
1644
  return { baseUrl: pending.baseUrl, success: result.success };
1620
1645
  })
1621
1646
  );
1622
- for (const result of refundResults) {
1647
+ for (const result of tokenRefundResults) {
1623
1648
  if (result.status === "fulfilled" && result.value.success) {
1624
1649
  this.storageAdapter.removeToken(result.value.baseUrl);
1625
1650
  }
1626
1651
  }
1652
+ const apiKeyRefundResults = await Promise.allSettled(
1653
+ apiKeysToRefund.map(async (apiKeyEntry) => {
1654
+ const fullApiKeyEntry = this.storageAdapter.getApiKey(
1655
+ apiKeyEntry.baseUrl
1656
+ );
1657
+ if (!fullApiKeyEntry) {
1658
+ return { baseUrl: apiKeyEntry.baseUrl, success: false };
1659
+ }
1660
+ const result = await this.refundApiKey({
1661
+ mintUrl,
1662
+ baseUrl: apiKeyEntry.baseUrl,
1663
+ apiKey: fullApiKeyEntry.key
1664
+ });
1665
+ return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
1666
+ })
1667
+ );
1668
+ for (const result of apiKeyRefundResults) {
1669
+ if (result.status === "fulfilled" && result.value.success) {
1670
+ this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
1671
+ }
1672
+ }
1627
1673
  }
1628
1674
  /**
1629
1675
  * Fetch refund token from provider API
@@ -2874,10 +2920,34 @@ var RoutstrClient = class {
2874
2920
  }
2875
2921
  }
2876
2922
  if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
2923
+ this.storageAdapter.getApiKey(baseUrl);
2924
+ let topupAmount = params.requiredSats;
2925
+ try {
2926
+ let currentBalance = 0;
2927
+ if (this.mode === "apikeys") {
2928
+ const currentBalanceInfo = await this.balanceManager.getTokenBalance(
2929
+ params.token,
2930
+ baseUrl
2931
+ );
2932
+ currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
2933
+ } else if (this.mode === "lazyrefund") {
2934
+ const distribution = this.storageAdapter.getCachedTokenDistribution();
2935
+ const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
2936
+ currentBalance = tokenEntry?.amount ?? 0;
2937
+ }
2938
+ const shortfall = Math.max(0, params.requiredSats - currentBalance);
2939
+ topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
2940
+ } catch (e) {
2941
+ this._log(
2942
+ "WARN",
2943
+ "Could not get current token balance for topup calculation:",
2944
+ e
2945
+ );
2946
+ }
2877
2947
  const topupResult = await this.balanceManager.topUp({
2878
2948
  mintUrl,
2879
2949
  baseUrl,
2880
- amount: params.requiredSats * TOPUP_MARGIN,
2950
+ amount: topupAmount * TOPUP_MARGIN,
2881
2951
  token: params.token
2882
2952
  });
2883
2953
  this._log(
@@ -2958,7 +3028,10 @@ var RoutstrClient = class {
2958
3028
  retryToken = latestBalanceInfo.apiKey;
2959
3029
  }
2960
3030
  if (latestTokenBalance >= 0) {
2961
- this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
3031
+ this.storageAdapter.updateApiKeyBalance(
3032
+ baseUrl,
3033
+ latestTokenBalance
3034
+ );
2962
3035
  }
2963
3036
  }
2964
3037
  } catch (error) {
@@ -3732,7 +3805,8 @@ var SDK_STORAGE_KEYS = {
3732
3805
  CHILD_KEYS: "child_keys",
3733
3806
  ROUTSTR21_MODELS: "routstr21Models",
3734
3807
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
3735
- CACHED_RECEIVE_TOKENS: "cached_receive_tokens"
3808
+ CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
3809
+ USAGE_TRACKING: "usage_tracking"
3736
3810
  };
3737
3811
 
3738
3812
  // storage/store.ts
@@ -3767,7 +3841,8 @@ var createSdkStore = async ({
3767
3841
  rawChildKeys,
3768
3842
  rawRoutstr21Models,
3769
3843
  rawLastRoutstr21ModelsUpdate,
3770
- rawCachedReceiveTokens
3844
+ rawCachedReceiveTokens,
3845
+ rawUsageTracking
3771
3846
  ] = await Promise.all([
3772
3847
  driver.getItem(
3773
3848
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3797,7 +3872,8 @@ var createSdkStore = async ({
3797
3872
  SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
3798
3873
  null
3799
3874
  ),
3800
- driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, [])
3875
+ driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
3876
+ driver.getItem(SDK_STORAGE_KEYS.USAGE_TRACKING, [])
3801
3877
  ]);
3802
3878
  const modelsFromAllProviders = Object.fromEntries(
3803
3879
  Object.entries(rawModels).map(([baseUrl, models]) => [
@@ -3855,6 +3931,7 @@ var createSdkStore = async ({
3855
3931
  unit: entry.unit || "sat",
3856
3932
  createdAt: entry.createdAt ?? Date.now()
3857
3933
  }));
3934
+ const usageTracking = rawUsageTracking;
3858
3935
  return vanilla.createStore((set, get) => ({
3859
3936
  modelsFromAllProviders,
3860
3937
  lastUsedModel,
@@ -3870,6 +3947,7 @@ var createSdkStore = async ({
3870
3947
  routstr21Models,
3871
3948
  lastRoutstr21ModelsUpdate,
3872
3949
  cachedReceiveTokens,
3950
+ usageTracking,
3873
3951
  setModelsFromAllProviders: (value) => {
3874
3952
  const normalized = {};
3875
3953
  for (const [baseUrl, models] of Object.entries(value)) {
@@ -3986,6 +4064,10 @@ var createSdkStore = async ({
3986
4064
  }));
3987
4065
  void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
3988
4066
  set({ cachedReceiveTokens: normalized });
4067
+ },
4068
+ setUsageTracking: (value) => {
4069
+ void driver.setItem(SDK_STORAGE_KEYS.USAGE_TRACKING, value);
4070
+ set({ usageTracking: value });
3989
4071
  }
3990
4072
  }));
3991
4073
  };