@routstr/sdk 0.2.5 → 0.2.6

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.
@@ -120,13 +120,8 @@ var CashuSpender = class {
120
120
  normalizedMintBalances[url] = balanceInSats;
121
121
  totalMintBalance += balanceInSats;
122
122
  }
123
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
124
123
  const providerBalances = {};
125
124
  let totalProviderBalance = 0;
126
- for (const pending of pendingDistribution) {
127
- providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
128
- totalProviderBalance += pending.amount;
129
- }
130
125
  const apiKeys = this.storageAdapter.getAllApiKeys();
131
126
  for (const apiKey of apiKeys) {
132
127
  if (!providerBalances[apiKey.baseUrl]) {
@@ -347,27 +342,11 @@ var CashuSpender = class {
347
342
  };
348
343
  }
349
344
  }
350
- if (token && baseUrl) {
351
- try {
352
- this.storageAdapter.setToken(baseUrl, token);
353
- } catch (error) {
354
- if (error instanceof Error && error.message.includes("Token already exists")) {
355
- this._log(
356
- "DEBUG",
357
- `[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
358
- );
359
- const receiveResult = await this.receiveToken(token);
360
- if (receiveResult.success) {
361
- this._log(
362
- "DEBUG",
363
- `[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
364
- );
365
- }
366
- token = this.storageAdapter.getToken(baseUrl);
367
- } else {
368
- throw error;
369
- }
370
- }
345
+ if (token) {
346
+ this._log(
347
+ "DEBUG",
348
+ `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
349
+ );
371
350
  }
372
351
  this._logTransaction("spend", {
373
352
  amount: spentAmount,
@@ -388,19 +367,19 @@ var CashuSpender = class {
388
367
  };
389
368
  }
390
369
  /**
391
- * Try to reuse an existing token
370
+ * Try to reuse an existing API key
392
371
  */
393
372
  async _tryReuseToken(baseUrl, amount, mintUrl) {
394
- const storedToken = this.storageAdapter.getToken(baseUrl);
395
- if (!storedToken) return null;
396
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
397
- const balanceForBaseUrl = pendingDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
398
- this._log("DEBUG", "RESUINGDSR GSODGNSD", balanceForBaseUrl, amount);
373
+ const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
374
+ if (!apiKeyEntry) return null;
375
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
376
+ const balanceForBaseUrl = apiKeyDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
377
+ this._log("DEBUG", "Reusing API key", balanceForBaseUrl, amount);
399
378
  if (balanceForBaseUrl > amount) {
400
379
  const units = this.walletAdapter.getMintUnits();
401
380
  const unit = units[mintUrl] || "sat";
402
381
  return {
403
- token: storedToken,
382
+ token: apiKeyEntry.key,
404
383
  status: "success",
405
384
  balance: balanceForBaseUrl,
406
385
  unit
@@ -411,7 +390,8 @@ var CashuSpender = class {
411
390
  const topUpResult = await this.balanceManager.topUp({
412
391
  mintUrl,
413
392
  baseUrl,
414
- amount: topUpAmount
393
+ amount: topUpAmount,
394
+ token: apiKeyEntry.key
415
395
  });
416
396
  this._log("DEBUG", "TOPUP ", topUpResult);
417
397
  if (topUpResult.success && topUpResult.toppedUpAmount) {
@@ -425,7 +405,7 @@ var CashuSpender = class {
425
405
  status: "success"
426
406
  });
427
407
  return {
428
- token: storedToken,
408
+ token: apiKeyEntry.key,
429
409
  status: "success",
430
410
  balance: newBalance,
431
411
  unit
@@ -433,84 +413,108 @@ var CashuSpender = class {
433
413
  }
434
414
  const providerBalance = await this._getProviderTokenBalance(
435
415
  baseUrl,
436
- storedToken
416
+ apiKeyEntry.key
437
417
  );
438
418
  this._log("DEBUG", providerBalance);
439
419
  if (providerBalance <= 0) {
440
- this.storageAdapter.removeToken(baseUrl);
420
+ this.storageAdapter.removeApiKey(baseUrl);
441
421
  }
442
422
  }
443
423
  return null;
444
424
  }
445
425
  /**
446
- * Refund specific providers without retrying spend
426
+ * Refund all xcashu tokens from storage and increment tryCounts on failure.
427
+ * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
428
+ * @param mintUrl - The mint URL for receiving tokens
429
+ * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
430
+ * @returns Results for each xcashu token refund attempt
447
431
  */
448
- async refundProviders(baseUrls, mintUrl, refundApiKeys = false, forceRefund) {
432
+ async refundXcashuTokens(mintUrl, excludeBaseUrls) {
449
433
  const results = [];
450
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
451
- const toRefund = pendingDistribution.filter(
452
- (p) => baseUrls.includes(p.baseUrl)
453
- );
454
- const refundResults = await Promise.allSettled(
455
- toRefund.map(async (pending) => {
456
- const token = this.storageAdapter.getToken(pending.baseUrl);
457
- this._log("DEBUG", token, this.balanceManager);
458
- if (!token || !this.balanceManager) {
459
- return { baseUrl: pending.baseUrl, success: false };
460
- }
461
- const tokenBalance = await this.balanceManager.getTokenBalance(
462
- token,
463
- pending.baseUrl
464
- );
465
- if (tokenBalance.reserved > 0) {
466
- return { baseUrl: pending.baseUrl, success: false };
467
- }
468
- const result = await this.balanceManager.refund({
469
- mintUrl,
470
- baseUrl: pending.baseUrl,
471
- token
472
- });
473
- this._log("DEBUG", result);
474
- if (result.success) {
475
- this.storageAdapter.removeToken(pending.baseUrl);
476
- }
477
- return { baseUrl: pending.baseUrl, success: result.success };
478
- })
479
- );
480
- results.push(
481
- ...refundResults.map(
482
- (r) => r.status === "fulfilled" ? r.value : { baseUrl: "", success: false }
483
- )
484
- );
485
- if (refundApiKeys) {
486
- const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
487
- const apiKeysToRefund = apiKeyDistribution.filter(
488
- (p) => baseUrls.includes(p.baseUrl)
489
- );
490
- for (const apiKeyEntry of apiKeysToRefund) {
491
- const apiKeyEntryFull = this.storageAdapter.getApiKey(
492
- apiKeyEntry.baseUrl
493
- );
494
- if (apiKeyEntryFull && this.balanceManager) {
495
- const refundResult = await this.balanceManager.refundApiKey({
496
- mintUrl,
497
- baseUrl: apiKeyEntry.baseUrl,
498
- apiKey: apiKeyEntryFull.key,
499
- forceRefund
500
- });
501
- if (refundResult.success) {
502
- this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
434
+ const xcashuTokens = this.storageAdapter.getXcashuTokens();
435
+ const excludedUrls = new Set(excludeBaseUrls || []);
436
+ for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
437
+ if (excludedUrls.has(baseUrl)) continue;
438
+ for (const xcashuToken of tokens) {
439
+ try {
440
+ const receiveResult = await this.receiveToken(xcashuToken.token);
441
+ if (receiveResult.success) {
442
+ this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
443
+ results.push({
444
+ baseUrl,
445
+ token: xcashuToken.token,
446
+ success: true
447
+ });
448
+ this._log(
449
+ "DEBUG",
450
+ `[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
451
+ );
452
+ } else {
453
+ const currentTryCount = xcashuToken.tryCount ?? 0;
454
+ const newTryCount = currentTryCount + 1;
455
+ this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
456
+ results.push({
457
+ baseUrl,
458
+ token: xcashuToken.token,
459
+ success: false,
460
+ error: receiveResult.message ?? "Refund failed"
461
+ });
462
+ this._log(
463
+ "DEBUG",
464
+ `[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
465
+ );
503
466
  }
467
+ } catch (error) {
468
+ const currentTryCount = xcashuToken.tryCount ?? 0;
469
+ const newTryCount = currentTryCount + 1;
470
+ this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
471
+ const errorMessage = error instanceof Error ? error.message : String(error);
504
472
  results.push({
505
- baseUrl: apiKeyEntry.baseUrl,
506
- success: refundResult.success
473
+ baseUrl,
474
+ token: xcashuToken.token,
475
+ success: false,
476
+ error: errorMessage
507
477
  });
478
+ this._log(
479
+ "ERROR",
480
+ `[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
481
+ );
482
+ }
483
+ }
484
+ }
485
+ return results;
486
+ }
487
+ /**
488
+ * Refund specific providers without retrying spend
489
+ */
490
+ async refundProviders(mintUrl, forceRefund) {
491
+ const results = [];
492
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
493
+ for (const apiKeyEntry of apiKeyDistribution) {
494
+ const apiKeyEntryFull = this.storageAdapter.getApiKey(
495
+ apiKeyEntry.baseUrl
496
+ );
497
+ if (apiKeyEntryFull && this.balanceManager) {
498
+ const refundResult = await this.balanceManager.refundApiKey({
499
+ mintUrl,
500
+ baseUrl: apiKeyEntry.baseUrl,
501
+ apiKey: apiKeyEntryFull.key,
502
+ forceRefund
503
+ });
504
+ if (refundResult.success) {
505
+ this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
508
506
  } else {
509
- results.push({
510
- baseUrl: apiKeyEntry.baseUrl,
511
- success: false
512
- });
507
+ this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
513
508
  }
509
+ results.push({
510
+ baseUrl: apiKeyEntry.baseUrl,
511
+ success: refundResult.success
512
+ });
513
+ } else {
514
+ results.push({
515
+ baseUrl: apiKeyEntry.baseUrl,
516
+ success: false
517
+ });
514
518
  }
515
519
  }
516
520
  return results;
@@ -595,13 +599,8 @@ var BalanceManager = class {
595
599
  normalizedMintBalances[url] = balanceInSats;
596
600
  totalMintBalance += balanceInSats;
597
601
  }
598
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
599
602
  const providerBalances = {};
600
603
  let totalProviderBalance = 0;
601
- for (const pending of pendingDistribution) {
602
- providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
603
- totalProviderBalance += pending.amount;
604
- }
605
604
  const apiKeys = this.storageAdapter.getAllApiKeys();
606
605
  for (const apiKey of apiKeys) {
607
606
  if (!providerBalances[apiKey.baseUrl]) {
@@ -616,57 +615,6 @@ var BalanceManager = class {
616
615
  mintBalances: normalizedMintBalances
617
616
  };
618
617
  }
619
- /**
620
- * Unified refund - handles both NIP-60 and legacy wallet refunds
621
- */
622
- async refund(options) {
623
- const { mintUrl, baseUrl, token: providedToken } = options;
624
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
625
- if (!storedToken) {
626
- console.log("[BalanceManager] No token to refund, returning early");
627
- return { success: true, message: "No API key to refund" };
628
- }
629
- let fetchResult;
630
- try {
631
- fetchResult = await this._fetchRefundToken(baseUrl, storedToken);
632
- if (!fetchResult.success) {
633
- return {
634
- success: false,
635
- message: fetchResult.error || "Refund failed",
636
- requestId: fetchResult.requestId
637
- };
638
- }
639
- if (!fetchResult.token) {
640
- return {
641
- success: false,
642
- message: "No token received from refund",
643
- requestId: fetchResult.requestId
644
- };
645
- }
646
- if (fetchResult.error === "No balance to refund") {
647
- console.log(
648
- "[BalanceManager] No balance to refund, removing stored token"
649
- );
650
- this.storageAdapter.removeToken(baseUrl);
651
- return { success: true, message: "No balance to refund" };
652
- }
653
- const receiveResult = await this.cashuSpender.receiveToken(
654
- fetchResult.token
655
- );
656
- const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
657
- if (!providedToken) {
658
- this.storageAdapter.removeToken(baseUrl);
659
- }
660
- return {
661
- success: receiveResult.success,
662
- refundedAmount: totalAmountMsat,
663
- requestId: fetchResult.requestId
664
- };
665
- } catch (error) {
666
- console.error("[BalanceManager] Refund error", error);
667
- return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
668
- }
669
- }
670
618
  /**
671
619
  * Refund API key balance - convert remaining API key balance to cashu token
672
620
  * @param options - Refund options including forceRefund flag
@@ -802,8 +750,9 @@ var BalanceManager = class {
802
750
  if (!amount || amount <= 0) {
803
751
  return { success: false, message: "Invalid top up amount" };
804
752
  }
805
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
806
- if (!storedToken) {
753
+ const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
754
+ const apiKey = providedToken || apiKeyEntry?.key;
755
+ if (!apiKey) {
807
756
  return { success: false, message: "No API key available for top up" };
808
757
  }
809
758
  let cashuToken = null;
@@ -823,7 +772,7 @@ var BalanceManager = class {
823
772
  cashuToken = tokenResult.token;
824
773
  const topUpResult = await this._postTopUp(
825
774
  baseUrl,
826
- storedToken,
775
+ apiKey,
827
776
  cashuToken
828
777
  );
829
778
  requestId = topUpResult.requestId;
@@ -1038,38 +987,11 @@ var BalanceManager = class {
1038
987
  return candidates;
1039
988
  }
1040
989
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
1041
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1042
990
  const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1043
991
  const forceRefund = retryCount >= 2;
1044
- const toRefund = pendingDistribution.filter(
1045
- (pending) => pending.baseUrl !== baseUrl
1046
- );
1047
992
  const apiKeysToRefund = apiKeyDistribution.filter(
1048
993
  (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1049
994
  );
1050
- const tokenRefundResults = await Promise.allSettled(
1051
- toRefund.map(async (pending) => {
1052
- const token = this.storageAdapter.getToken(pending.baseUrl);
1053
- if (!token) {
1054
- return { baseUrl: pending.baseUrl, success: false };
1055
- }
1056
- const tokenBalance = await this.getTokenBalance(token, pending.baseUrl);
1057
- if (tokenBalance.reserved > 0) {
1058
- return { baseUrl: pending.baseUrl, success: false };
1059
- }
1060
- const result = await this.refund({
1061
- mintUrl,
1062
- baseUrl: pending.baseUrl,
1063
- token
1064
- });
1065
- return { baseUrl: pending.baseUrl, success: result.success };
1066
- })
1067
- );
1068
- for (const result of tokenRefundResults) {
1069
- if (result.status === "fulfilled" && result.value.success) {
1070
- this.storageAdapter.removeToken(result.value.baseUrl);
1071
- }
1072
- }
1073
995
  const apiKeyRefundResults = await Promise.allSettled(
1074
996
  apiKeysToRefund.map(async (apiKeyEntry) => {
1075
997
  const fullApiKeyEntry = this.storageAdapter.getApiKey(
@@ -1093,77 +1015,6 @@ var BalanceManager = class {
1093
1015
  }
1094
1016
  }
1095
1017
  }
1096
- /**
1097
- * Fetch refund token from provider API
1098
- */
1099
- async _fetchRefundToken(baseUrl, storedToken) {
1100
- if (!baseUrl) {
1101
- return {
1102
- success: false,
1103
- error: "No base URL configured"
1104
- };
1105
- }
1106
- const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1107
- const url = `${normalizedBaseUrl}v1/wallet/refund`;
1108
- const controller = new AbortController();
1109
- const timeoutId = setTimeout(() => {
1110
- controller.abort();
1111
- }, 6e4);
1112
- try {
1113
- const response = await fetch(url, {
1114
- method: "POST",
1115
- headers: {
1116
- Authorization: `Bearer ${storedToken}`,
1117
- "Content-Type": "application/json"
1118
- },
1119
- signal: controller.signal
1120
- });
1121
- clearTimeout(timeoutId);
1122
- const requestId = response.headers.get("x-routstr-request-id") || void 0;
1123
- if (!response.ok) {
1124
- const errorData = await response.json().catch(() => ({}));
1125
- if (response.status === 400 && errorData?.detail === "No balance to refund") {
1126
- this.storageAdapter.removeToken(baseUrl);
1127
- return {
1128
- success: false,
1129
- requestId,
1130
- error: "No balance to refund"
1131
- };
1132
- }
1133
- return {
1134
- success: false,
1135
- requestId,
1136
- error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
1137
- };
1138
- }
1139
- const data = await response.json();
1140
- console.log("refund rsule", data);
1141
- return {
1142
- success: true,
1143
- token: data.token,
1144
- requestId
1145
- };
1146
- } catch (error) {
1147
- clearTimeout(timeoutId);
1148
- console.error("[BalanceManager._fetchRefundToken] Fetch error", error);
1149
- if (error instanceof Error) {
1150
- if (error.name === "AbortError") {
1151
- return {
1152
- success: false,
1153
- error: "Request timed out after 1 minute"
1154
- };
1155
- }
1156
- return {
1157
- success: false,
1158
- error: error.message
1159
- };
1160
- }
1161
- return {
1162
- success: false,
1163
- error: "Unknown error occurred during refund request"
1164
- };
1165
- }
1166
- }
1167
1018
  /**
1168
1019
  * Post topup request to provider API
1169
1020
  */