@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.
@@ -2,9 +2,9 @@
2
2
 
3
3
  // core/errors.ts
4
4
  var InsufficientBalanceError = class extends Error {
5
- constructor(required, available, maxMintBalance = 0, maxMintUrl = "") {
5
+ constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
6
6
  super(
7
- `Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
7
+ customMessage ?? `Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
8
8
  );
9
9
  this.required = required;
10
10
  this.available = available;
@@ -108,6 +108,9 @@ var CashuSpender = class {
108
108
  return result;
109
109
  }
110
110
  async _getBalanceState() {
111
+ if (this.balanceManager) {
112
+ return this.balanceManager.getBalanceState();
113
+ }
111
114
  const mintBalances = await this.walletAdapter.getBalances();
112
115
  const units = this.walletAdapter.getMintUnits();
113
116
  let totalMintBalance = 0;
@@ -123,15 +126,16 @@ var CashuSpender = class {
123
126
  const providerBalances = {};
124
127
  let totalProviderBalance = 0;
125
128
  for (const pending of pendingDistribution) {
126
- providerBalances[pending.baseUrl] = pending.amount;
129
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
127
130
  totalProviderBalance += pending.amount;
128
131
  }
129
132
  const apiKeys = this.storageAdapter.getAllApiKeys();
130
133
  for (const apiKey of apiKeys) {
131
134
  if (!providerBalances[apiKey.baseUrl]) {
132
- providerBalances[apiKey.baseUrl] = apiKey.balance;
133
- totalProviderBalance += apiKey.balance;
135
+ providerBalances[apiKey.baseUrl] = 0;
134
136
  }
137
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
138
+ totalProviderBalance += apiKey.balance;
135
139
  }
136
140
  return {
137
141
  totalBalance: totalMintBalance + totalProviderBalance,
@@ -280,25 +284,12 @@ var CashuSpender = class {
280
284
  `[CashuSpender] _spendInternal: Could not reuse token, will create new token`
281
285
  );
282
286
  }
283
- const balances = await this.walletAdapter.getBalances();
284
- const units = this.walletAdapter.getMintUnits();
285
- let totalBalance = 0;
286
- for (const url in balances) {
287
- const balance = balances[url];
288
- const unit = units[url];
289
- const balanceInSats = getBalanceInSats(balance, unit);
290
- totalBalance += balanceInSats;
291
- }
292
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
293
- const totalPending = pendingDistribution.reduce(
294
- (sum, item) => sum + item.amount,
295
- 0
296
- );
287
+ const balanceState = await this._getBalanceState();
288
+ const totalAvailableBalance = balanceState.totalBalance;
297
289
  this._log(
298
290
  "DEBUG",
299
- `[CashuSpender] _spendInternal: totalBalance=${totalBalance}, totalPending=${totalPending}, adjustedAmount=${adjustedAmount}`
291
+ `[CashuSpender] _spendInternal: totalAvailableBalance=${totalAvailableBalance}, adjustedAmount=${adjustedAmount}`
300
292
  );
301
- const totalAvailableBalance = totalBalance + totalPending;
302
293
  if (totalAvailableBalance < adjustedAmount) {
303
294
  this._log(
304
295
  "ERROR",
@@ -306,8 +297,7 @@ var CashuSpender = class {
306
297
  );
307
298
  return this._createInsufficientBalanceError(
308
299
  adjustedAmount,
309
- balances,
310
- units,
300
+ balanceState.mintBalances,
311
301
  totalAvailableBalance
312
302
  );
313
303
  }
@@ -327,8 +317,7 @@ var CashuSpender = class {
327
317
  if ((tokenResult.error || "").includes("Insufficient balance")) {
328
318
  return this._createInsufficientBalanceError(
329
319
  adjustedAmount,
330
- balances,
331
- units,
320
+ balanceState.mintBalances,
332
321
  totalAvailableBalance
333
322
  );
334
323
  }
@@ -373,6 +362,7 @@ var CashuSpender = class {
373
362
  "DEBUG",
374
363
  `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
375
364
  );
365
+ const units = this.walletAdapter.getMintUnits();
376
366
  return {
377
367
  token,
378
368
  status: "success",
@@ -510,13 +500,11 @@ var CashuSpender = class {
510
500
  /**
511
501
  * Create an insufficient balance error result
512
502
  */
513
- _createInsufficientBalanceError(required, balances, units, availableBalance) {
503
+ _createInsufficientBalanceError(required, normalizedBalances, availableBalance) {
514
504
  let maxBalance = 0;
515
505
  let maxMintUrl = "";
516
- for (const mintUrl in balances) {
517
- const balance = balances[mintUrl];
518
- const unit = units[mintUrl];
519
- const balanceInSats = getBalanceInSats(balance, unit);
506
+ for (const mintUrl in normalizedBalances) {
507
+ const balanceInSats = normalizedBalances[mintUrl];
520
508
  if (balanceInSats > maxBalance) {
521
509
  maxBalance = balanceInSats;
522
510
  maxMintUrl = mintUrl;
@@ -577,6 +565,39 @@ var BalanceManager = class {
577
565
  }
578
566
  }
579
567
  cashuSpender;
568
+ async getBalanceState() {
569
+ const mintBalances = await this.walletAdapter.getBalances();
570
+ const units = this.walletAdapter.getMintUnits();
571
+ let totalMintBalance = 0;
572
+ const normalizedMintBalances = {};
573
+ for (const url in mintBalances) {
574
+ const balance = mintBalances[url];
575
+ const unit = units[url];
576
+ const balanceInSats = getBalanceInSats(balance, unit);
577
+ normalizedMintBalances[url] = balanceInSats;
578
+ totalMintBalance += balanceInSats;
579
+ }
580
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
581
+ const providerBalances = {};
582
+ let totalProviderBalance = 0;
583
+ for (const pending of pendingDistribution) {
584
+ providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
585
+ totalProviderBalance += pending.amount;
586
+ }
587
+ const apiKeys = this.storageAdapter.getAllApiKeys();
588
+ for (const apiKey of apiKeys) {
589
+ if (!providerBalances[apiKey.baseUrl]) {
590
+ providerBalances[apiKey.baseUrl] = 0;
591
+ }
592
+ providerBalances[apiKey.baseUrl] += apiKey.balance;
593
+ totalProviderBalance += apiKey.balance;
594
+ }
595
+ return {
596
+ totalBalance: totalMintBalance + totalProviderBalance,
597
+ providerBalances,
598
+ mintBalances: normalizedMintBalances
599
+ };
600
+ }
580
601
  /**
581
602
  * Unified refund - handles both NIP-60 and legacy wallet refunds
582
603
  */
@@ -787,6 +808,10 @@ var BalanceManager = class {
787
808
  requestId
788
809
  };
789
810
  } catch (error) {
811
+ console.log(
812
+ "DEBUG",
813
+ `[TopuPU] topup: Topup result for ${baseUrl}: error=${error}`
814
+ );
790
815
  if (cashuToken) {
791
816
  await this._recoverFailedTopUp(cashuToken);
792
817
  }
@@ -806,23 +831,42 @@ var BalanceManager = class {
806
831
  if (!adjustedAmount || isNaN(adjustedAmount)) {
807
832
  return { success: false, error: "Invalid top up amount" };
808
833
  }
834
+ const balanceState = await this.getBalanceState();
809
835
  const balances = await this.walletAdapter.getBalances();
810
836
  const units = this.walletAdapter.getMintUnits();
811
- let totalMintBalance = 0;
812
- for (const url in balances) {
813
- const unit = units[url];
814
- const balanceInSats = getBalanceInSats(balances[url], unit);
815
- totalMintBalance += balanceInSats;
816
- }
817
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
818
- const refundablePending = pendingDistribution.filter((entry) => entry.baseUrl !== baseUrl).reduce((sum, entry) => sum + entry.amount, 0);
819
- if (totalMintBalance < adjustedAmount && totalMintBalance + refundablePending >= adjustedAmount && retryCount < 1) {
837
+ const totalMintBalance = Object.values(balanceState.mintBalances).reduce(
838
+ (sum, value) => sum + value,
839
+ 0
840
+ );
841
+ const targetProviderBalance = balanceState.providerBalances[baseUrl] || 0;
842
+ const refundableProviderBalance = Object.entries(
843
+ balanceState.providerBalances
844
+ ).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
845
+ if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 1) {
820
846
  await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
821
847
  return this.createProviderToken({
822
848
  ...options,
823
849
  retryCount: retryCount + 1
824
850
  });
825
851
  }
852
+ if (totalMintBalance + targetProviderBalance < adjustedAmount) {
853
+ const error = new InsufficientBalanceError(
854
+ adjustedAmount,
855
+ totalMintBalance + targetProviderBalance,
856
+ totalMintBalance,
857
+ Object.entries(balanceState.mintBalances).reduce(
858
+ (max, [url, balance]) => balance > max.balance ? { url, balance } : max,
859
+ { url: "", balance: 0 }
860
+ ).url
861
+ );
862
+ return { success: false, error: error.message };
863
+ }
864
+ if (targetProviderBalance >= adjustedAmount) {
865
+ return {
866
+ success: true,
867
+ amountSpent: 0
868
+ };
869
+ }
826
870
  const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
827
871
  let requiredAmount = adjustedAmount;
828
872
  const supportedMintsOnly = providerMints.length > 0;
@@ -938,10 +982,14 @@ var BalanceManager = class {
938
982
  }
939
983
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
940
984
  const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
985
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
941
986
  const toRefund = pendingDistribution.filter(
942
987
  (pending) => pending.baseUrl !== baseUrl
943
988
  );
944
- const refundResults = await Promise.allSettled(
989
+ const apiKeysToRefund = apiKeyDistribution.filter(
990
+ (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
991
+ );
992
+ const tokenRefundResults = await Promise.allSettled(
945
993
  toRefund.map(async (pending) => {
946
994
  const token = this.storageAdapter.getToken(pending.baseUrl);
947
995
  if (!token) {
@@ -959,11 +1007,32 @@ var BalanceManager = class {
959
1007
  return { baseUrl: pending.baseUrl, success: result.success };
960
1008
  })
961
1009
  );
962
- for (const result of refundResults) {
1010
+ for (const result of tokenRefundResults) {
963
1011
  if (result.status === "fulfilled" && result.value.success) {
964
1012
  this.storageAdapter.removeToken(result.value.baseUrl);
965
1013
  }
966
1014
  }
1015
+ const apiKeyRefundResults = await Promise.allSettled(
1016
+ apiKeysToRefund.map(async (apiKeyEntry) => {
1017
+ const fullApiKeyEntry = this.storageAdapter.getApiKey(
1018
+ apiKeyEntry.baseUrl
1019
+ );
1020
+ if (!fullApiKeyEntry) {
1021
+ return { baseUrl: apiKeyEntry.baseUrl, success: false };
1022
+ }
1023
+ const result = await this.refundApiKey({
1024
+ mintUrl,
1025
+ baseUrl: apiKeyEntry.baseUrl,
1026
+ apiKey: fullApiKeyEntry.key
1027
+ });
1028
+ return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
1029
+ })
1030
+ );
1031
+ for (const result of apiKeyRefundResults) {
1032
+ if (result.status === "fulfilled" && result.value.success) {
1033
+ this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
1034
+ }
1035
+ }
967
1036
  }
968
1037
  /**
969
1038
  * Fetch refund token from provider API