@routstr/sdk 0.2.6 → 0.2.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.
@@ -87,6 +87,15 @@ interface SdkStorageState {
87
87
  createdAt: number;
88
88
  lastUsed?: number | null;
89
89
  }>;
90
+ /** Set of failed provider URLs */
91
+ failedProviders: string[];
92
+ /** Map of provider URL -> timestamp of last failure */
93
+ lastFailed: Record<string, number>;
94
+ /** Providers currently on cooldown: [baseUrl, timestamp][] */
95
+ providersOnCooldown: Array<{
96
+ baseUrl: string;
97
+ timestamp: number;
98
+ }>;
90
99
  }
91
100
 
92
101
  interface SdkStoreOptions {
@@ -137,6 +146,18 @@ interface SdkStorageStore extends SdkStorageState {
137
146
  createdAt?: number;
138
147
  lastUsed?: number | null;
139
148
  }> | ((current: SdkStorageStore["clientIds"]) => SdkStorageStore["clientIds"])) => void;
149
+ setFailedProviders: (value: string[]) => void;
150
+ addFailedProvider: (baseUrl: string) => void;
151
+ removeFailedProvider: (baseUrl: string) => void;
152
+ setLastFailed: (value: Record<string, number>) => void;
153
+ setLastFailedTimestamp: (baseUrl: string, timestamp: number) => void;
154
+ setProvidersOnCooldown: (value: Array<{
155
+ baseUrl: string;
156
+ timestamp: number;
157
+ }>) => void;
158
+ addProviderOnCooldown: (baseUrl: string, timestamp: number) => void;
159
+ removeProviderFromCooldown: (baseUrl: string) => void;
160
+ clearProvidersOnCooldown: () => void;
140
161
  }
141
162
  /** Store type returned after async initialization */
142
163
  type SdkStore = StoreApi<SdkStorageStore>;
@@ -87,6 +87,15 @@ interface SdkStorageState {
87
87
  createdAt: number;
88
88
  lastUsed?: number | null;
89
89
  }>;
90
+ /** Set of failed provider URLs */
91
+ failedProviders: string[];
92
+ /** Map of provider URL -> timestamp of last failure */
93
+ lastFailed: Record<string, number>;
94
+ /** Providers currently on cooldown: [baseUrl, timestamp][] */
95
+ providersOnCooldown: Array<{
96
+ baseUrl: string;
97
+ timestamp: number;
98
+ }>;
90
99
  }
91
100
 
92
101
  interface SdkStoreOptions {
@@ -137,6 +146,18 @@ interface SdkStorageStore extends SdkStorageState {
137
146
  createdAt?: number;
138
147
  lastUsed?: number | null;
139
148
  }> | ((current: SdkStorageStore["clientIds"]) => SdkStorageStore["clientIds"])) => void;
149
+ setFailedProviders: (value: string[]) => void;
150
+ addFailedProvider: (baseUrl: string) => void;
151
+ removeFailedProvider: (baseUrl: string) => void;
152
+ setLastFailed: (value: Record<string, number>) => void;
153
+ setLastFailedTimestamp: (baseUrl: string, timestamp: number) => void;
154
+ setProvidersOnCooldown: (value: Array<{
155
+ baseUrl: string;
156
+ timestamp: number;
157
+ }>) => void;
158
+ addProviderOnCooldown: (baseUrl: string, timestamp: number) => void;
159
+ removeProviderFromCooldown: (baseUrl: string) => void;
160
+ clearProvidersOnCooldown: () => void;
140
161
  }
141
162
  /** Store type returned after async initialization */
142
163
  type SdkStore = StoreApi<SdkStorageStore>;
@@ -78,9 +78,14 @@ declare class BalanceManager {
78
78
  */
79
79
  refundApiKey(options: RefundApiKeyOptions): Promise<RefundResult>;
80
80
  /**
81
- * Fetch refund token from provider API using API key authentication
81
+ * Fetch refund token from provider API using API key (or xcashu token) authentication
82
82
  */
83
- private _fetchRefundTokenWithApiKey;
83
+ fetchRefundToken(baseUrl: string, apiKeyOrToken: string, xCashu?: boolean): Promise<{
84
+ success: boolean;
85
+ token?: string;
86
+ requestId?: string;
87
+ error?: string;
88
+ }>;
84
89
  /**
85
90
  * Top up API key balance with a cashu token
86
91
  */
@@ -167,6 +172,7 @@ declare class CashuSpender {
167
172
  unit: "sat" | "msat";
168
173
  message?: string;
169
174
  }>;
175
+ private _decodeTokenAmount;
170
176
  private _getBalanceState;
171
177
  private _logTransaction;
172
178
  /**
@@ -194,8 +200,9 @@ declare class CashuSpender {
194
200
  */
195
201
  private _tryReuseToken;
196
202
  /**
197
- * Refund all xcashu tokens from storage and increment tryCounts on failure.
198
- * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
203
+ * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
204
+ * The xcashu token acts as an API key to claim the refund, and the response contains
205
+ * the actual refunded Cashu token which is then received into the wallet.
199
206
  * @param mintUrl - The mint URL for receiving tokens
200
207
  * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
201
208
  * @returns Results for each xcashu token refund attempt
@@ -78,9 +78,14 @@ declare class BalanceManager {
78
78
  */
79
79
  refundApiKey(options: RefundApiKeyOptions): Promise<RefundResult>;
80
80
  /**
81
- * Fetch refund token from provider API using API key authentication
81
+ * Fetch refund token from provider API using API key (or xcashu token) authentication
82
82
  */
83
- private _fetchRefundTokenWithApiKey;
83
+ fetchRefundToken(baseUrl: string, apiKeyOrToken: string, xCashu?: boolean): Promise<{
84
+ success: boolean;
85
+ token?: string;
86
+ requestId?: string;
87
+ error?: string;
88
+ }>;
84
89
  /**
85
90
  * Top up API key balance with a cashu token
86
91
  */
@@ -167,6 +172,7 @@ declare class CashuSpender {
167
172
  unit: "sat" | "msat";
168
173
  message?: string;
169
174
  }>;
175
+ private _decodeTokenAmount;
170
176
  private _getBalanceState;
171
177
  private _logTransaction;
172
178
  /**
@@ -194,8 +200,9 @@ declare class CashuSpender {
194
200
  */
195
201
  private _tryReuseToken;
196
202
  /**
197
- * Refund all xcashu tokens from storage and increment tryCounts on failure.
198
- * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
203
+ * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
204
+ * The xcashu token acts as an API key to claim the refund, and the response contains
205
+ * the actual refunded Cashu token which is then received into the wallet.
199
206
  * @param mintUrl - The mint URL for receiving tokens
200
207
  * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
201
208
  * @returns Results for each xcashu token refund attempt
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var cashuTs = require('@cashu/cashu-ts');
4
+
3
5
  // core/errors.ts
4
6
  var InsufficientBalanceError = class extends Error {
5
7
  constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
@@ -77,8 +79,6 @@ function selectMintWithBalance(balances, units, amount, excludeMints = []) {
77
79
  }
78
80
  return { selectedMintUrl: null, selectedMintBalance: 0 };
79
81
  }
80
-
81
- // wallet/CashuSpender.ts
82
82
  var CashuSpender = class {
83
83
  constructor(walletAdapter, storageAdapter, _providerRegistry, balanceManager) {
84
84
  this.walletAdapter = walletAdapter;
@@ -89,23 +89,43 @@ var CashuSpender = class {
89
89
  _isBusy = false;
90
90
  debugLevel = "WARN";
91
91
  async receiveToken(token) {
92
- const result = await this.walletAdapter.receiveToken(token);
93
- if (!result.success && result.message?.includes("Failed to fetch mint")) {
94
- const cachedTokens = this.storageAdapter.getCachedReceiveTokens();
95
- const existingIndex = cachedTokens.findIndex((t) => t.token === token);
96
- if (existingIndex === -1) {
97
- this.storageAdapter.setCachedReceiveTokens([
98
- ...cachedTokens,
99
- {
100
- token,
101
- amount: result.amount,
102
- unit: result.unit,
103
- createdAt: Date.now()
104
- }
105
- ]);
92
+ try {
93
+ const result = await this.walletAdapter.receiveToken(token);
94
+ return result;
95
+ } catch (error) {
96
+ const errorMessage = error instanceof Error ? error.message : String(error);
97
+ if (errorMessage.includes("Failed to fetch mint")) {
98
+ const cachedTokens = this.storageAdapter.getCachedReceiveTokens();
99
+ const existingIndex = cachedTokens.findIndex((t) => t.token === token);
100
+ if (existingIndex === -1) {
101
+ const { amount: amount2, unit: unit2 } = this._decodeTokenAmount(token);
102
+ this.storageAdapter.setCachedReceiveTokens([
103
+ ...cachedTokens,
104
+ {
105
+ token,
106
+ amount: amount2,
107
+ unit: unit2,
108
+ createdAt: Date.now()
109
+ }
110
+ ]);
111
+ }
106
112
  }
113
+ const { amount, unit } = this._decodeTokenAmount(token);
114
+ return { success: false, amount, unit, message: errorMessage };
115
+ }
116
+ }
117
+ _decodeTokenAmount(token) {
118
+ try {
119
+ const decoded = cashuTs.getDecodedToken(token);
120
+ const amount = decoded.proofs.reduce(
121
+ (acc, proof) => acc + proof.amount,
122
+ 0
123
+ );
124
+ const unit = decoded.unit || "sat";
125
+ return { amount, unit };
126
+ } catch {
127
+ return { amount: 0, unit: "sat" };
107
128
  }
108
- return result;
109
129
  }
110
130
  async _getBalanceState() {
111
131
  if (this.balanceManager) {
@@ -425,8 +445,9 @@ var CashuSpender = class {
425
445
  return null;
426
446
  }
427
447
  /**
428
- * Refund all xcashu tokens from storage and increment tryCounts on failure.
429
- * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
448
+ * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
449
+ * The xcashu token acts as an API key to claim the refund, and the response contains
450
+ * the actual refunded Cashu token which is then received into the wallet.
430
451
  * @param mintUrl - The mint URL for receiving tokens
431
452
  * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
432
453
  * @returns Results for each xcashu token refund attempt
@@ -439,7 +460,20 @@ var CashuSpender = class {
439
460
  if (excludedUrls.has(baseUrl)) continue;
440
461
  for (const xcashuToken of tokens) {
441
462
  try {
442
- const receiveResult = await this.receiveToken(xcashuToken.token);
463
+ if (!this.balanceManager) {
464
+ throw new Error("BalanceManager not available for xcashu refund");
465
+ }
466
+ const fetchResult = await this.balanceManager.fetchRefundToken(
467
+ baseUrl,
468
+ xcashuToken.token,
469
+ true
470
+ );
471
+ if (!fetchResult.success || !fetchResult.token) {
472
+ throw new Error(
473
+ fetchResult.error || "Failed to fetch refund token from provider"
474
+ );
475
+ }
476
+ const receiveResult = await this.receiveToken(fetchResult.token);
443
477
  if (receiveResult.success) {
444
478
  this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
445
479
  results.push({
@@ -454,7 +488,10 @@ var CashuSpender = class {
454
488
  } else {
455
489
  const currentTryCount = xcashuToken.tryCount ?? 0;
456
490
  const newTryCount = currentTryCount + 1;
457
- this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
491
+ this.storageAdapter.updateXcashuTokenTryCount(
492
+ xcashuToken.token,
493
+ newTryCount
494
+ );
458
495
  results.push({
459
496
  baseUrl,
460
497
  token: xcashuToken.token,
@@ -463,13 +500,16 @@ var CashuSpender = class {
463
500
  });
464
501
  this._log(
465
502
  "DEBUG",
466
- `[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
503
+ `[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}: ${receiveResult.message}`
467
504
  );
468
505
  }
469
506
  } catch (error) {
470
507
  const currentTryCount = xcashuToken.tryCount ?? 0;
471
508
  const newTryCount = currentTryCount + 1;
472
- this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
509
+ this.storageAdapter.updateXcashuTokenTryCount(
510
+ xcashuToken.token,
511
+ newTryCount
512
+ );
473
513
  const errorMessage = error instanceof Error ? error.message : String(error);
474
514
  results.push({
475
515
  baseUrl,
@@ -506,7 +546,10 @@ var CashuSpender = class {
506
546
  if (refundResult.success) {
507
547
  this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
508
548
  } else {
509
- this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
549
+ this.storageAdapter.updateApiKeyBalance(
550
+ apiKeyEntry.baseUrl,
551
+ apiKeyEntry.amount
552
+ );
510
553
  }
511
554
  results.push({
512
555
  baseUrl: apiKeyEntry.baseUrl,
@@ -644,7 +687,7 @@ var BalanceManager = class {
644
687
  }
645
688
  let fetchResult;
646
689
  try {
647
- fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
690
+ fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
648
691
  if (!fetchResult.success) {
649
692
  return {
650
693
  success: false,
@@ -672,6 +715,7 @@ var BalanceManager = class {
672
715
  return {
673
716
  success: receiveResult.success,
674
717
  refundedAmount: totalAmountMsat,
718
+ message: receiveResult.message,
675
719
  requestId: fetchResult.requestId
676
720
  };
677
721
  } catch (error) {
@@ -680,9 +724,9 @@ var BalanceManager = class {
680
724
  }
681
725
  }
682
726
  /**
683
- * Fetch refund token from provider API using API key authentication
727
+ * Fetch refund token from provider API using API key (or xcashu token) authentication
684
728
  */
685
- async _fetchRefundTokenWithApiKey(baseUrl, apiKey) {
729
+ async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
686
730
  if (!baseUrl) {
687
731
  return {
688
732
  success: false,
@@ -696,12 +740,17 @@ var BalanceManager = class {
696
740
  controller.abort();
697
741
  }, 6e4);
698
742
  try {
743
+ const headers = {
744
+ "Content-Type": "application/json"
745
+ };
746
+ if (xCashu) {
747
+ headers["X-Cashu"] = apiKeyOrToken;
748
+ } else {
749
+ headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
750
+ }
699
751
  const response = await fetch(url, {
700
752
  method: "POST",
701
- headers: {
702
- Authorization: `Bearer ${apiKey}`,
703
- "Content-Type": "application/json"
704
- },
753
+ headers,
705
754
  signal: controller.signal
706
755
  });
707
756
  clearTimeout(timeoutId);
@@ -722,10 +771,7 @@ var BalanceManager = class {
722
771
  };
723
772
  } catch (error) {
724
773
  clearTimeout(timeoutId);
725
- console.error(
726
- "[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
727
- error
728
- );
774
+ console.error("[BalanceManager.fetchRefundToken] Fetch error", error);
729
775
  if (error instanceof Error) {
730
776
  if (error.name === "AbortError") {
731
777
  return {
@@ -772,11 +818,7 @@ var BalanceManager = class {
772
818
  };
773
819
  }
774
820
  cashuToken = tokenResult.token;
775
- const topUpResult = await this._postTopUp(
776
- baseUrl,
777
- apiKey,
778
- cashuToken
779
- );
821
+ const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
780
822
  requestId = topUpResult.requestId;
781
823
  console.log(topUpResult);
782
824
  if (!topUpResult.success) {
@@ -1142,7 +1184,7 @@ var BalanceManager = class {
1142
1184
  console.log(response.status);
1143
1185
  const data = await response.json();
1144
1186
  console.log("FAILED ", data);
1145
- const isInvalidApiKey = response.status === 401 && data?.code === "invalid_api_key" && data?.message?.includes("proofs already spent");
1187
+ const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
1146
1188
  return {
1147
1189
  amount: -1,
1148
1190
  reserved: data.reserved ?? 0,