@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.mjs CHANGED
@@ -766,6 +766,9 @@ var CashuSpender = class {
766
766
  return result;
767
767
  }
768
768
  async _getBalanceState() {
769
+ if (this.balanceManager) {
770
+ return this.balanceManager.getBalanceState();
771
+ }
769
772
  const mintBalances = await this.walletAdapter.getBalances();
770
773
  const units = this.walletAdapter.getMintUnits();
771
774
  let totalMintBalance = 0;
@@ -781,15 +784,16 @@ var CashuSpender = class {
781
784
  const providerBalances = {};
782
785
  let totalProviderBalance = 0;
783
786
  for (const pending of pendingDistribution) {
784
- providerBalances[pending.baseUrl] = pending.amount;
787
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
785
788
  totalProviderBalance += pending.amount;
786
789
  }
787
790
  const apiKeys = this.storageAdapter.getAllApiKeys();
788
791
  for (const apiKey of apiKeys) {
789
792
  if (!providerBalances[apiKey.baseUrl]) {
790
- providerBalances[apiKey.baseUrl] = apiKey.balance;
791
- totalProviderBalance += apiKey.balance;
793
+ providerBalances[apiKey.baseUrl] = 0;
792
794
  }
795
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
796
+ totalProviderBalance += apiKey.balance;
793
797
  }
794
798
  return {
795
799
  totalBalance: totalMintBalance + totalProviderBalance,
@@ -938,25 +942,12 @@ var CashuSpender = class {
938
942
  `[CashuSpender] _spendInternal: Could not reuse token, will create new token`
939
943
  );
940
944
  }
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
- );
945
+ const balanceState = await this._getBalanceState();
946
+ const totalAvailableBalance = balanceState.totalBalance;
955
947
  this._log(
956
948
  "DEBUG",
957
- `[CashuSpender] _spendInternal: totalBalance=${totalBalance}, totalPending=${totalPending}, adjustedAmount=${adjustedAmount}`
949
+ `[CashuSpender] _spendInternal: totalAvailableBalance=${totalAvailableBalance}, adjustedAmount=${adjustedAmount}`
958
950
  );
959
- const totalAvailableBalance = totalBalance + totalPending;
960
951
  if (totalAvailableBalance < adjustedAmount) {
961
952
  this._log(
962
953
  "ERROR",
@@ -964,8 +955,7 @@ var CashuSpender = class {
964
955
  );
965
956
  return this._createInsufficientBalanceError(
966
957
  adjustedAmount,
967
- balances,
968
- units,
958
+ balanceState.mintBalances,
969
959
  totalAvailableBalance
970
960
  );
971
961
  }
@@ -985,8 +975,7 @@ var CashuSpender = class {
985
975
  if ((tokenResult.error || "").includes("Insufficient balance")) {
986
976
  return this._createInsufficientBalanceError(
987
977
  adjustedAmount,
988
- balances,
989
- units,
978
+ balanceState.mintBalances,
990
979
  totalAvailableBalance
991
980
  );
992
981
  }
@@ -1031,6 +1020,7 @@ var CashuSpender = class {
1031
1020
  "DEBUG",
1032
1021
  `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
1033
1022
  );
1023
+ const units = this.walletAdapter.getMintUnits();
1034
1024
  return {
1035
1025
  token,
1036
1026
  status: "success",
@@ -1168,13 +1158,11 @@ var CashuSpender = class {
1168
1158
  /**
1169
1159
  * Create an insufficient balance error result
1170
1160
  */
1171
- _createInsufficientBalanceError(required, balances, units, availableBalance) {
1161
+ _createInsufficientBalanceError(required, normalizedBalances, availableBalance) {
1172
1162
  let maxBalance = 0;
1173
1163
  let maxMintUrl = "";
1174
- for (const mintUrl in balances) {
1175
- const balance = balances[mintUrl];
1176
- const unit = units[mintUrl];
1177
- const balanceInSats = getBalanceInSats(balance, unit);
1164
+ for (const mintUrl in normalizedBalances) {
1165
+ const balanceInSats = normalizedBalances[mintUrl];
1178
1166
  if (balanceInSats > maxBalance) {
1179
1167
  maxBalance = balanceInSats;
1180
1168
  maxMintUrl = mintUrl;
@@ -1235,6 +1223,39 @@ var BalanceManager = class {
1235
1223
  }
1236
1224
  }
1237
1225
  cashuSpender;
1226
+ async getBalanceState() {
1227
+ const mintBalances = await this.walletAdapter.getBalances();
1228
+ const units = this.walletAdapter.getMintUnits();
1229
+ let totalMintBalance = 0;
1230
+ const normalizedMintBalances = {};
1231
+ for (const url in mintBalances) {
1232
+ const balance = mintBalances[url];
1233
+ const unit = units[url];
1234
+ const balanceInSats = getBalanceInSats(balance, unit);
1235
+ normalizedMintBalances[url] = balanceInSats;
1236
+ totalMintBalance += balanceInSats;
1237
+ }
1238
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1239
+ const providerBalances = {};
1240
+ let totalProviderBalance = 0;
1241
+ for (const pending of pendingDistribution) {
1242
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
1243
+ totalProviderBalance += pending.amount;
1244
+ }
1245
+ const apiKeys = this.storageAdapter.getAllApiKeys();
1246
+ for (const apiKey of apiKeys) {
1247
+ if (!providerBalances[apiKey.baseUrl]) {
1248
+ providerBalances[apiKey.baseUrl] = 0;
1249
+ }
1250
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
1251
+ totalProviderBalance += apiKey.balance;
1252
+ }
1253
+ return {
1254
+ totalBalance: totalMintBalance + totalProviderBalance,
1255
+ providerBalances,
1256
+ mintBalances: normalizedMintBalances
1257
+ };
1258
+ }
1238
1259
  /**
1239
1260
  * Unified refund - handles both NIP-60 and legacy wallet refunds
1240
1261
  */
@@ -1464,17 +1485,17 @@ var BalanceManager = class {
1464
1485
  if (!adjustedAmount || isNaN(adjustedAmount)) {
1465
1486
  return { success: false, error: "Invalid top up amount" };
1466
1487
  }
1488
+ const balanceState = await this.getBalanceState();
1467
1489
  const balances = await this.walletAdapter.getBalances();
1468
1490
  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) {
1491
+ const totalMintBalance = Object.values(balanceState.mintBalances).reduce(
1492
+ (sum, value) => sum + value,
1493
+ 0
1494
+ );
1495
+ const refundableProviderBalance = Object.entries(
1496
+ balanceState.providerBalances
1497
+ ).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
1498
+ if (totalMintBalance < adjustedAmount && totalMintBalance + refundableProviderBalance >= adjustedAmount && retryCount < 1) {
1478
1499
  await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
1479
1500
  return this.createProviderToken({
1480
1501
  ...options,
@@ -1596,10 +1617,14 @@ var BalanceManager = class {
1596
1617
  }
1597
1618
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
1598
1619
  const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1620
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1599
1621
  const toRefund = pendingDistribution.filter(
1600
1622
  (pending) => pending.baseUrl !== baseUrl
1601
1623
  );
1602
- const refundResults = await Promise.allSettled(
1624
+ const apiKeysToRefund = apiKeyDistribution.filter(
1625
+ (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1626
+ );
1627
+ const tokenRefundResults = await Promise.allSettled(
1603
1628
  toRefund.map(async (pending) => {
1604
1629
  const token = this.storageAdapter.getToken(pending.baseUrl);
1605
1630
  if (!token) {
@@ -1617,11 +1642,32 @@ var BalanceManager = class {
1617
1642
  return { baseUrl: pending.baseUrl, success: result.success };
1618
1643
  })
1619
1644
  );
1620
- for (const result of refundResults) {
1645
+ for (const result of tokenRefundResults) {
1621
1646
  if (result.status === "fulfilled" && result.value.success) {
1622
1647
  this.storageAdapter.removeToken(result.value.baseUrl);
1623
1648
  }
1624
1649
  }
1650
+ const apiKeyRefundResults = await Promise.allSettled(
1651
+ apiKeysToRefund.map(async (apiKeyEntry) => {
1652
+ const fullApiKeyEntry = this.storageAdapter.getApiKey(
1653
+ apiKeyEntry.baseUrl
1654
+ );
1655
+ if (!fullApiKeyEntry) {
1656
+ return { baseUrl: apiKeyEntry.baseUrl, success: false };
1657
+ }
1658
+ const result = await this.refundApiKey({
1659
+ mintUrl,
1660
+ baseUrl: apiKeyEntry.baseUrl,
1661
+ apiKey: fullApiKeyEntry.key
1662
+ });
1663
+ return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
1664
+ })
1665
+ );
1666
+ for (const result of apiKeyRefundResults) {
1667
+ if (result.status === "fulfilled" && result.value.success) {
1668
+ this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
1669
+ }
1670
+ }
1625
1671
  }
1626
1672
  /**
1627
1673
  * Fetch refund token from provider API
@@ -2872,10 +2918,34 @@ var RoutstrClient = class {
2872
2918
  }
2873
2919
  }
2874
2920
  if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
2921
+ this.storageAdapter.getApiKey(baseUrl);
2922
+ let topupAmount = params.requiredSats;
2923
+ try {
2924
+ let currentBalance = 0;
2925
+ if (this.mode === "apikeys") {
2926
+ const currentBalanceInfo = await this.balanceManager.getTokenBalance(
2927
+ params.token,
2928
+ baseUrl
2929
+ );
2930
+ currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
2931
+ } else if (this.mode === "lazyrefund") {
2932
+ const distribution = this.storageAdapter.getCachedTokenDistribution();
2933
+ const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
2934
+ currentBalance = tokenEntry?.amount ?? 0;
2935
+ }
2936
+ const shortfall = Math.max(0, params.requiredSats - currentBalance);
2937
+ topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
2938
+ } catch (e) {
2939
+ this._log(
2940
+ "WARN",
2941
+ "Could not get current token balance for topup calculation:",
2942
+ e
2943
+ );
2944
+ }
2875
2945
  const topupResult = await this.balanceManager.topUp({
2876
2946
  mintUrl,
2877
2947
  baseUrl,
2878
- amount: params.requiredSats * TOPUP_MARGIN,
2948
+ amount: topupAmount * TOPUP_MARGIN,
2879
2949
  token: params.token
2880
2950
  });
2881
2951
  this._log(
@@ -2956,7 +3026,10 @@ var RoutstrClient = class {
2956
3026
  retryToken = latestBalanceInfo.apiKey;
2957
3027
  }
2958
3028
  if (latestTokenBalance >= 0) {
2959
- this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
3029
+ this.storageAdapter.updateApiKeyBalance(
3030
+ baseUrl,
3031
+ latestTokenBalance
3032
+ );
2960
3033
  }
2961
3034
  }
2962
3035
  } catch (error) {
@@ -3730,7 +3803,8 @@ var SDK_STORAGE_KEYS = {
3730
3803
  CHILD_KEYS: "child_keys",
3731
3804
  ROUTSTR21_MODELS: "routstr21Models",
3732
3805
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
3733
- CACHED_RECEIVE_TOKENS: "cached_receive_tokens"
3806
+ CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
3807
+ USAGE_TRACKING: "usage_tracking"
3734
3808
  };
3735
3809
 
3736
3810
  // storage/store.ts
@@ -3765,7 +3839,8 @@ var createSdkStore = async ({
3765
3839
  rawChildKeys,
3766
3840
  rawRoutstr21Models,
3767
3841
  rawLastRoutstr21ModelsUpdate,
3768
- rawCachedReceiveTokens
3842
+ rawCachedReceiveTokens,
3843
+ rawUsageTracking
3769
3844
  ] = await Promise.all([
3770
3845
  driver.getItem(
3771
3846
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3795,7 +3870,8 @@ var createSdkStore = async ({
3795
3870
  SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
3796
3871
  null
3797
3872
  ),
3798
- driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, [])
3873
+ driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
3874
+ driver.getItem(SDK_STORAGE_KEYS.USAGE_TRACKING, [])
3799
3875
  ]);
3800
3876
  const modelsFromAllProviders = Object.fromEntries(
3801
3877
  Object.entries(rawModels).map(([baseUrl, models]) => [
@@ -3853,6 +3929,7 @@ var createSdkStore = async ({
3853
3929
  unit: entry.unit || "sat",
3854
3930
  createdAt: entry.createdAt ?? Date.now()
3855
3931
  }));
3932
+ const usageTracking = rawUsageTracking;
3856
3933
  return createStore((set, get) => ({
3857
3934
  modelsFromAllProviders,
3858
3935
  lastUsedModel,
@@ -3868,6 +3945,7 @@ var createSdkStore = async ({
3868
3945
  routstr21Models,
3869
3946
  lastRoutstr21ModelsUpdate,
3870
3947
  cachedReceiveTokens,
3948
+ usageTracking,
3871
3949
  setModelsFromAllProviders: (value) => {
3872
3950
  const normalized = {};
3873
3951
  for (const [baseUrl, models] of Object.entries(value)) {
@@ -3984,6 +4062,10 @@ var createSdkStore = async ({
3984
4062
  }));
3985
4063
  void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
3986
4064
  set({ cachedReceiveTokens: normalized });
4065
+ },
4066
+ setUsageTracking: (value) => {
4067
+ void driver.setItem(SDK_STORAGE_KEYS.USAGE_TRACKING, value);
4068
+ set({ usageTracking: value });
3987
4069
  }
3988
4070
  }));
3989
4071
  };