@routstr/sdk 0.1.0 → 0.1.2

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
@@ -1,3 +1,6 @@
1
+ import { RelayPool, onlyEvents } from 'applesauce-relay';
2
+ import { EventStore } from 'applesauce-core';
3
+ import { tap } from 'rxjs';
1
4
  import { createStore } from 'zustand/vanilla';
2
5
  import { getDecodedToken } from '@cashu/cashu-ts';
3
6
 
@@ -97,13 +100,11 @@ var MintDiscoveryError = class extends Error {
97
100
  this.name = "MintDiscoveryError";
98
101
  }
99
102
  };
100
-
101
- // discovery/ModelManager.ts
102
103
  var ModelManager = class _ModelManager {
103
104
  constructor(adapter, config = {}) {
104
105
  this.adapter = adapter;
105
106
  this.providerDirectoryUrl = config.providerDirectoryUrl || "https://api.routstr.com/v1/providers/";
106
- this.cacheTTL = config.cacheTTL || 21 * 60 * 1e3;
107
+ this.cacheTTL = config.cacheTTL || 210 * 60 * 1e3;
107
108
  this.includeProviderUrls = config.includeProviderUrls || [];
108
109
  this.excludeProviderUrls = config.excludeProviderUrls || [];
109
110
  }
@@ -128,21 +129,132 @@ var ModelManager = class _ModelManager {
128
129
  }
129
130
  /**
130
131
  * Bootstrap provider list from the provider directory
131
- * Fetches available providers and caches their base URLs
132
+ * First tries to fetch from Nostr (kind 30421), falls back to HTTP
132
133
  * @param torMode Whether running in Tor context
133
134
  * @returns Array of provider base URLs
134
135
  * @throws ProviderBootstrapError if all providers fail to fetch
135
136
  */
136
137
  async bootstrapProviders(torMode = false) {
138
+ const cachedUrls = this.adapter.getBaseUrlsList();
139
+ if (cachedUrls.length > 0) {
140
+ const lastUpdate = this.adapter.getBaseUrlsLastUpdate();
141
+ const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
142
+ if (cacheValid) {
143
+ return this.filterBaseUrlsForTor(cachedUrls, torMode);
144
+ }
145
+ }
137
146
  try {
138
- const cachedUrls = this.adapter.getBaseUrlsList();
139
- if (cachedUrls.length > 0) {
140
- const lastUpdate = this.adapter.getBaseUrlsLastUpdate();
141
- const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
142
- if (cacheValid) {
143
- return this.filterBaseUrlsForTor(cachedUrls, torMode);
147
+ const nostrProviders = await this.bootstrapFromNostr(38421, torMode);
148
+ if (nostrProviders.length > 0) {
149
+ const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);
150
+ this.adapter.setBaseUrlsList(filtered);
151
+ this.adapter.setBaseUrlsLastUpdate(Date.now());
152
+ await this.fetchRoutstr21Models();
153
+ return filtered;
154
+ }
155
+ } catch (e) {
156
+ console.warn("Nostr bootstrap failed, falling back to HTTP:", e);
157
+ }
158
+ return this.bootstrapFromHttp(torMode);
159
+ }
160
+ /**
161
+ * Bootstrap providers from Nostr network (kind 30421)
162
+ * @param kind The Nostr kind to fetch
163
+ * @param torMode Whether running in Tor context
164
+ * @returns Array of provider base URLs
165
+ */
166
+ async bootstrapFromNostr(kind, torMode) {
167
+ const DEFAULT_RELAYS = [
168
+ "wss://relay.primal.net",
169
+ "wss://nos.lol",
170
+ "wss://relay.routstr.com"
171
+ ];
172
+ const pool = new RelayPool();
173
+ const localEventStore = new EventStore();
174
+ const timeoutMs = 5e3;
175
+ await new Promise((resolve) => {
176
+ pool.req(DEFAULT_RELAYS, {
177
+ kinds: [kind],
178
+ limit: 100
179
+ }).pipe(
180
+ onlyEvents(),
181
+ tap((event) => {
182
+ localEventStore.add(event);
183
+ })
184
+ ).subscribe({
185
+ complete: () => {
186
+ resolve();
187
+ }
188
+ });
189
+ setTimeout(() => {
190
+ resolve();
191
+ }, timeoutMs);
192
+ });
193
+ const timeline = localEventStore.getTimeline({ kinds: [kind] });
194
+ const bases = /* @__PURE__ */ new Set();
195
+ for (const event of timeline) {
196
+ const eventUrls = [];
197
+ for (const tag of event.tags) {
198
+ if (tag[0] === "u" && typeof tag[1] === "string") {
199
+ eventUrls.push(tag[1]);
200
+ }
201
+ }
202
+ if (eventUrls.length > 0) {
203
+ for (const url of eventUrls) {
204
+ const normalized = this.normalizeUrl(url);
205
+ if (!torMode || normalized.includes(".onion")) {
206
+ bases.add(normalized);
207
+ }
208
+ }
209
+ continue;
210
+ }
211
+ try {
212
+ const content = JSON.parse(event.content);
213
+ const providers = Array.isArray(content) ? content : content.providers || [];
214
+ for (const p of providers) {
215
+ const endpoints = this.getProviderEndpoints(p, torMode);
216
+ for (const endpoint of endpoints) {
217
+ bases.add(endpoint);
218
+ }
219
+ }
220
+ } catch {
221
+ try {
222
+ const providers = JSON.parse(event.content);
223
+ if (Array.isArray(providers)) {
224
+ for (const p of providers) {
225
+ const endpoints = this.getProviderEndpoints(p, torMode);
226
+ for (const endpoint of endpoints) {
227
+ bases.add(endpoint);
228
+ }
229
+ }
230
+ }
231
+ } catch {
232
+ console.warn(
233
+ "[NostrBootstrap] Failed to parse Nostr event content:",
234
+ event.id
235
+ );
144
236
  }
145
237
  }
238
+ }
239
+ for (const url of this.includeProviderUrls) {
240
+ const normalized = this.normalizeUrl(url);
241
+ if (!torMode || normalized.includes(".onion")) {
242
+ bases.add(normalized);
243
+ }
244
+ }
245
+ const excluded = new Set(
246
+ this.excludeProviderUrls.map((url) => this.normalizeUrl(url))
247
+ );
248
+ const result = Array.from(bases).filter((base) => !excluded.has(base));
249
+ return result;
250
+ }
251
+ /**
252
+ * Bootstrap providers from HTTP endpoint
253
+ * @param torMode Whether running in Tor context
254
+ * @returns Array of provider base URLs
255
+ */
256
+ async bootstrapFromHttp(torMode) {
257
+ try {
146
258
  const res = await fetch(this.providerDirectoryUrl);
147
259
  if (!res.ok) {
148
260
  throw new Error(`Failed to fetch providers: ${res.status}`);
@@ -165,13 +277,11 @@ var ModelManager = class _ModelManager {
165
277
  const excluded = new Set(
166
278
  this.excludeProviderUrls.map((url) => this.normalizeUrl(url))
167
279
  );
168
- const list = Array.from(bases).filter((base) => {
169
- if (excluded.has(base)) return false;
170
- return true;
171
- });
280
+ const list = Array.from(bases).filter((base) => !excluded.has(base));
172
281
  if (list.length > 0) {
173
282
  this.adapter.setBaseUrlsList(list);
174
283
  this.adapter.setBaseUrlsLastUpdate(Date.now());
284
+ await this.fetchRoutstr21Models();
175
285
  }
176
286
  return list;
177
287
  } catch (e) {
@@ -338,6 +448,57 @@ var ModelManager = class _ModelManager {
338
448
  }
339
449
  return url.endsWith("/") ? url : `${url}/`;
340
450
  }
451
+ /**
452
+ * Fetch routstr21 models from Nostr network (kind 38423)
453
+ * @returns Array of model IDs or empty array if not found
454
+ */
455
+ async fetchRoutstr21Models() {
456
+ const DEFAULT_RELAYS = [
457
+ "wss://relay.primal.net",
458
+ "wss://nos.lol",
459
+ "wss://relay.routstr.com"
460
+ ];
461
+ const pool = new RelayPool();
462
+ const localEventStore = new EventStore();
463
+ const timeoutMs = 5e3;
464
+ await new Promise((resolve) => {
465
+ pool.req(DEFAULT_RELAYS, {
466
+ kinds: [38423],
467
+ "#d": ["routstr-21-models"],
468
+ limit: 1,
469
+ authors: ["4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8"]
470
+ }).pipe(
471
+ onlyEvents(),
472
+ tap((event2) => {
473
+ localEventStore.add(event2);
474
+ })
475
+ ).subscribe({
476
+ complete: () => {
477
+ resolve();
478
+ }
479
+ });
480
+ setTimeout(() => {
481
+ resolve();
482
+ }, timeoutMs);
483
+ });
484
+ const timeline = localEventStore.getTimeline({ kinds: [38423] });
485
+ if (timeline.length === 0) {
486
+ return [];
487
+ }
488
+ const event = timeline[0];
489
+ try {
490
+ const content = JSON.parse(event.content);
491
+ const models = Array.isArray(content?.models) ? content.models : [];
492
+ this.adapter.setRoutstr21Models(models);
493
+ return models;
494
+ } catch {
495
+ console.warn(
496
+ "[Routstr21Models] Failed to parse Nostr event content:",
497
+ event.id
498
+ );
499
+ return [];
500
+ }
501
+ }
341
502
  };
342
503
 
343
504
  // discovery/MintDiscovery.ts
@@ -538,15 +699,55 @@ var AuditLogger = class _AuditLogger {
538
699
  };
539
700
  var auditLogger = AuditLogger.getInstance();
540
701
 
702
+ // wallet/tokenUtils.ts
703
+ function isNetworkErrorMessage(message) {
704
+ return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
705
+ }
706
+ function getBalanceInSats(balance, unit) {
707
+ return unit === "msat" ? balance / 1e3 : balance;
708
+ }
709
+ function selectMintWithBalance(balances, units, amount, excludeMints = []) {
710
+ for (const mintUrl in balances) {
711
+ if (excludeMints.includes(mintUrl)) {
712
+ continue;
713
+ }
714
+ const balanceInSats = getBalanceInSats(balances[mintUrl], units[mintUrl]);
715
+ if (balanceInSats >= amount) {
716
+ return { selectedMintUrl: mintUrl, selectedMintBalance: balanceInSats };
717
+ }
718
+ }
719
+ return { selectedMintUrl: null, selectedMintBalance: 0 };
720
+ }
721
+
541
722
  // wallet/CashuSpender.ts
542
723
  var CashuSpender = class {
543
- constructor(walletAdapter, storageAdapter, providerRegistry, balanceManager) {
724
+ constructor(walletAdapter, storageAdapter, _providerRegistry, balanceManager) {
544
725
  this.walletAdapter = walletAdapter;
545
726
  this.storageAdapter = storageAdapter;
546
- this.providerRegistry = providerRegistry;
727
+ this._providerRegistry = _providerRegistry;
547
728
  this.balanceManager = balanceManager;
548
729
  }
549
730
  _isBusy = false;
731
+ debugLevel = "WARN";
732
+ async receiveToken(token) {
733
+ const result = await this.walletAdapter.receiveToken(token);
734
+ if (!result.success && result.message?.includes("Failed to fetch mint")) {
735
+ const cachedTokens = this.storageAdapter.getCachedReceiveTokens();
736
+ const existingIndex = cachedTokens.findIndex((t) => t.token === token);
737
+ if (existingIndex === -1) {
738
+ this.storageAdapter.setCachedReceiveTokens([
739
+ ...cachedTokens,
740
+ {
741
+ token,
742
+ amount: result.amount,
743
+ unit: result.unit,
744
+ createdAt: Date.now()
745
+ }
746
+ ]);
747
+ }
748
+ }
749
+ return result;
750
+ }
550
751
  async _getBalanceState() {
551
752
  const mintBalances = await this.walletAdapter.getBalances();
552
753
  const units = this.walletAdapter.getMintUnits();
@@ -555,11 +756,11 @@ var CashuSpender = class {
555
756
  for (const url in mintBalances) {
556
757
  const balance = mintBalances[url];
557
758
  const unit = units[url];
558
- const balanceInSats = unit === "msat" ? balance / 1e3 : balance;
759
+ const balanceInSats = getBalanceInSats(balance, unit);
559
760
  normalizedMintBalances[url] = balanceInSats;
560
761
  totalMintBalance += balanceInSats;
561
762
  }
562
- const pendingDistribution = this.storageAdapter.getPendingTokenDistribution();
763
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
563
764
  const providerBalances = {};
564
765
  let totalProviderBalance = 0;
565
766
  for (const pending of pendingDistribution) {
@@ -589,8 +790,35 @@ var CashuSpender = class {
589
790
  get isBusy() {
590
791
  return this._isBusy;
591
792
  }
793
+ getDebugLevel() {
794
+ return this.debugLevel;
795
+ }
796
+ setDebugLevel(level) {
797
+ this.debugLevel = level;
798
+ }
799
+ _log(level, ...args) {
800
+ const levelPriority = {
801
+ DEBUG: 0,
802
+ WARN: 1,
803
+ ERROR: 2
804
+ };
805
+ if (levelPriority[level] >= levelPriority[this.debugLevel]) {
806
+ switch (level) {
807
+ case "DEBUG":
808
+ console.log(...args);
809
+ break;
810
+ case "WARN":
811
+ console.warn(...args);
812
+ break;
813
+ case "ERROR":
814
+ console.error(...args);
815
+ break;
816
+ }
817
+ }
818
+ }
592
819
  /**
593
820
  * Spend Cashu tokens with automatic mint selection and retry logic
821
+ * Throws errors on failure instead of returning failed SpendResult
594
822
  */
595
823
  async spend(options) {
596
824
  const {
@@ -604,7 +832,7 @@ var CashuSpender = class {
604
832
  } = options;
605
833
  this._isBusy = true;
606
834
  try {
607
- return await this._spendInternal({
835
+ const result = await this._spendInternal({
608
836
  mintUrl,
609
837
  amount,
610
838
  baseUrl,
@@ -613,10 +841,34 @@ var CashuSpender = class {
613
841
  excludeMints,
614
842
  retryCount
615
843
  });
844
+ if (result.status === "failed" || !result.token) {
845
+ const errorMsg = result.error || `Insufficient balance. Need ${amount} sats.`;
846
+ if (this._isNetworkError(errorMsg)) {
847
+ throw new Error(
848
+ `Your mint ${mintUrl} is unreachable or is blocking your IP. Please try again later or switch mints.`
849
+ );
850
+ }
851
+ if (result.errorDetails) {
852
+ throw new InsufficientBalanceError(
853
+ result.errorDetails.required,
854
+ result.errorDetails.available,
855
+ result.errorDetails.maxMintBalance,
856
+ result.errorDetails.maxMintUrl
857
+ );
858
+ }
859
+ throw new Error(errorMsg);
860
+ }
861
+ return result;
616
862
  } finally {
617
863
  this._isBusy = false;
618
864
  }
619
865
  }
866
+ /**
867
+ * Check if error message indicates a network error
868
+ */
869
+ _isNetworkError(message) {
870
+ return isNetworkErrorMessage(message) || message.includes("Your mint") && message.includes("unreachable");
871
+ }
620
872
  /**
621
873
  * Internal spending logic
622
874
  */
@@ -630,8 +882,16 @@ var CashuSpender = class {
630
882
  excludeMints,
631
883
  retryCount
632
884
  } = options;
885
+ this._log(
886
+ "DEBUG",
887
+ `[CashuSpender] _spendInternal: amount=${amount}, mintUrl=${mintUrl}, baseUrl=${baseUrl}, reuseToken=${reuseToken}`
888
+ );
633
889
  let adjustedAmount = Math.ceil(amount);
634
890
  if (!adjustedAmount || isNaN(adjustedAmount)) {
891
+ this._log(
892
+ "ERROR",
893
+ `[CashuSpender] _spendInternal: Invalid amount: ${amount}`
894
+ );
635
895
  return {
636
896
  token: null,
637
897
  status: "failed",
@@ -640,14 +900,26 @@ var CashuSpender = class {
640
900
  };
641
901
  }
642
902
  if (reuseToken && baseUrl) {
903
+ this._log(
904
+ "DEBUG",
905
+ `[CashuSpender] _spendInternal: Attempting to reuse token for ${baseUrl}`
906
+ );
643
907
  const existingResult = await this._tryReuseToken(
644
908
  baseUrl,
645
909
  adjustedAmount,
646
910
  mintUrl
647
911
  );
648
912
  if (existingResult) {
913
+ this._log(
914
+ "DEBUG",
915
+ `[CashuSpender] _spendInternal: Successfully reused token, balance: ${existingResult.balance}`
916
+ );
649
917
  return existingResult;
650
918
  }
919
+ this._log(
920
+ "DEBUG",
921
+ `[CashuSpender] _spendInternal: Could not reuse token, will create new token`
922
+ );
651
923
  }
652
924
  const balances = await this.walletAdapter.getBalances();
653
925
  const units = this.walletAdapter.getMintUnits();
@@ -655,19 +927,24 @@ var CashuSpender = class {
655
927
  for (const url in balances) {
656
928
  const balance = balances[url];
657
929
  const unit = units[url];
658
- const balanceInSats = unit === "msat" ? balance / 1e3 : balance;
930
+ const balanceInSats = getBalanceInSats(balance, unit);
659
931
  totalBalance += balanceInSats;
660
932
  }
661
- const pendingDistribution = this.storageAdapter.getPendingTokenDistribution();
933
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
662
934
  const totalPending = pendingDistribution.reduce(
663
935
  (sum, item) => sum + item.amount,
664
936
  0
665
937
  );
666
- if (totalBalance < adjustedAmount && totalPending + totalBalance > adjustedAmount && (retryCount ?? 0) < 1) {
667
- return await this._refundAndRetry(options);
668
- }
938
+ this._log(
939
+ "DEBUG",
940
+ `[CashuSpender] _spendInternal: totalBalance=${totalBalance}, totalPending=${totalPending}, adjustedAmount=${adjustedAmount}`
941
+ );
669
942
  const totalAvailableBalance = totalBalance + totalPending;
670
943
  if (totalAvailableBalance < adjustedAmount) {
944
+ this._log(
945
+ "ERROR",
946
+ `[CashuSpender] _spendInternal: Insufficient balance, have=${totalAvailableBalance}, need=${adjustedAmount}`
947
+ );
671
948
  return this._createInsufficientBalanceError(
672
949
  adjustedAmount,
673
950
  balances,
@@ -675,72 +952,73 @@ var CashuSpender = class {
675
952
  totalAvailableBalance
676
953
  );
677
954
  }
678
- let { selectedMintUrl, selectedMintBalance } = this._selectMintWithBalance(
679
- balances,
680
- units,
681
- adjustedAmount,
682
- excludeMints
683
- );
684
- if (selectedMintUrl && baseUrl && this.providerRegistry) {
685
- const providerMints = this.providerRegistry.getProviderMints(baseUrl);
686
- if (providerMints.length > 0 && !providerMints.includes(selectedMintUrl)) {
687
- const alternateResult = await this._findAlternateMint(
688
- options,
689
- balances,
690
- units,
691
- providerMints
692
- );
693
- if (alternateResult) {
694
- return alternateResult;
955
+ let token = null;
956
+ let selectedMintUrl;
957
+ let spentAmount = adjustedAmount;
958
+ if (this.balanceManager) {
959
+ const tokenResult = await this.balanceManager.createProviderToken({
960
+ mintUrl,
961
+ baseUrl,
962
+ amount: adjustedAmount,
963
+ p2pkPubkey,
964
+ excludeMints,
965
+ retryCount
966
+ });
967
+ if (!tokenResult.success || !tokenResult.token) {
968
+ if ((tokenResult.error || "").includes("Insufficient balance")) {
969
+ return this._createInsufficientBalanceError(
970
+ adjustedAmount,
971
+ balances,
972
+ units,
973
+ totalAvailableBalance
974
+ );
695
975
  }
696
- adjustedAmount += 2;
976
+ return {
977
+ token: null,
978
+ status: "failed",
979
+ balance: 0,
980
+ error: tokenResult.error || "Failed to create token"
981
+ };
697
982
  }
698
- }
699
- const activeMintBalance = balances[mintUrl] || 0;
700
- const activeMintUnit = units[mintUrl];
701
- const activeMintBalanceInSats = activeMintUnit === "msat" ? activeMintBalance / 1e3 : activeMintBalance;
702
- let token = null;
703
- if (activeMintBalanceInSats >= adjustedAmount && (baseUrl === "" || !this.providerRegistry)) {
983
+ token = tokenResult.token;
984
+ selectedMintUrl = tokenResult.selectedMintUrl;
985
+ spentAmount = tokenResult.amountSpent || adjustedAmount;
986
+ } else {
704
987
  try {
705
988
  token = await this.walletAdapter.sendToken(
706
989
  mintUrl,
707
990
  adjustedAmount,
708
991
  p2pkPubkey
709
992
  );
993
+ selectedMintUrl = mintUrl;
710
994
  } catch (error) {
711
- return this._handleSendError(error, options, balances, units);
712
- }
713
- } else if (selectedMintUrl && selectedMintBalance >= adjustedAmount) {
714
- try {
715
- token = await this.walletAdapter.sendToken(
716
- selectedMintUrl,
717
- adjustedAmount,
718
- p2pkPubkey
719
- );
720
- } catch (error) {
721
- return this._handleSendError(error, options, balances, units);
995
+ const errorMsg = error instanceof Error ? error.message : String(error);
996
+ return {
997
+ token: null,
998
+ status: "failed",
999
+ balance: 0,
1000
+ error: `Error generating token: ${errorMsg}`
1001
+ };
722
1002
  }
723
- } else {
724
- return this._createInsufficientBalanceError(
725
- adjustedAmount,
726
- balances,
727
- units
728
- );
729
1003
  }
730
1004
  if (token && baseUrl) {
731
1005
  this.storageAdapter.setToken(baseUrl, token);
732
1006
  }
733
1007
  this._logTransaction("spend", {
734
- amount: adjustedAmount,
1008
+ amount: spentAmount,
735
1009
  mintUrl: selectedMintUrl || mintUrl,
736
1010
  baseUrl,
737
1011
  status: "success"
738
1012
  });
1013
+ this._log(
1014
+ "DEBUG",
1015
+ `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
1016
+ );
739
1017
  return {
740
1018
  token,
741
1019
  status: "success",
742
- balance: adjustedAmount,
743
- unit: activeMintUnit
1020
+ balance: spentAmount,
1021
+ unit: (selectedMintUrl ? units[selectedMintUrl] : units[mintUrl]) || "sat"
744
1022
  };
745
1023
  }
746
1024
  /**
@@ -749,9 +1027,9 @@ var CashuSpender = class {
749
1027
  async _tryReuseToken(baseUrl, amount, mintUrl) {
750
1028
  const storedToken = this.storageAdapter.getToken(baseUrl);
751
1029
  if (!storedToken) return null;
752
- const pendingDistribution = this.storageAdapter.getPendingTokenDistribution();
1030
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
753
1031
  const balanceForBaseUrl = pendingDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
754
- console.log("RESUINGDSR GSODGNSD", balanceForBaseUrl, amount);
1032
+ this._log("DEBUG", "RESUINGDSR GSODGNSD", balanceForBaseUrl, amount);
755
1033
  if (balanceForBaseUrl > amount) {
756
1034
  const units = this.walletAdapter.getMintUnits();
757
1035
  const unit = units[mintUrl] || "sat";
@@ -769,7 +1047,7 @@ var CashuSpender = class {
769
1047
  baseUrl,
770
1048
  amount: topUpAmount
771
1049
  });
772
- console.log("TOPUP ", topUpResult);
1050
+ this._log("DEBUG", "TOPUP ", topUpResult);
773
1051
  if (topUpResult.success && topUpResult.toppedUpAmount) {
774
1052
  const newBalance = balanceForBaseUrl + topUpResult.toppedUpAmount;
775
1053
  const units = this.walletAdapter.getMintUnits();
@@ -791,7 +1069,7 @@ var CashuSpender = class {
791
1069
  baseUrl,
792
1070
  storedToken
793
1071
  );
794
- console.log(providerBalance);
1072
+ this._log("DEBUG", providerBalance);
795
1073
  if (providerBalance <= 0) {
796
1074
  this.storageAdapter.removeToken(baseUrl);
797
1075
  }
@@ -799,18 +1077,25 @@ var CashuSpender = class {
799
1077
  return null;
800
1078
  }
801
1079
  /**
802
- * Refund pending tokens and retry
1080
+ * Refund specific providers without retrying spend
803
1081
  */
804
- async _refundAndRetry(options) {
805
- const { mintUrl, baseUrl, retryCount } = options;
806
- const pendingDistribution = this.storageAdapter.getPendingTokenDistribution();
1082
+ async refundProviders(baseUrls, mintUrl, refundApiKeys = false) {
1083
+ const results = [];
1084
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1085
+ const toRefund = pendingDistribution.filter(
1086
+ (p) => baseUrls.includes(p.baseUrl)
1087
+ );
807
1088
  const refundResults = await Promise.allSettled(
808
- pendingDistribution.map(async (pending) => {
1089
+ toRefund.map(async (pending) => {
809
1090
  const token = this.storageAdapter.getToken(pending.baseUrl);
810
- if (!token || !this.balanceManager || pending.baseUrl === baseUrl) {
1091
+ this._log("DEBUG", token, this.balanceManager);
1092
+ if (!token || !this.balanceManager) {
811
1093
  return { baseUrl: pending.baseUrl, success: false };
812
1094
  }
813
- const tokenBalance = await this.balanceManager.getTokenBalance(token, pending.baseUrl);
1095
+ const tokenBalance = await this.balanceManager.getTokenBalance(
1096
+ token,
1097
+ pending.baseUrl
1098
+ );
814
1099
  if (tokenBalance.reserved > 0) {
815
1100
  return { baseUrl: pending.baseUrl, success: false };
816
1101
  }
@@ -819,120 +1104,49 @@ var CashuSpender = class {
819
1104
  baseUrl: pending.baseUrl,
820
1105
  token
821
1106
  });
1107
+ this._log("DEBUG", result);
1108
+ if (result.success) {
1109
+ this.storageAdapter.removeToken(pending.baseUrl);
1110
+ }
822
1111
  return { baseUrl: pending.baseUrl, success: result.success };
823
1112
  })
824
1113
  );
825
- for (const result of refundResults) {
826
- const refundResult = result.status === "fulfilled" ? result.value : { baseUrl: "", success: false };
827
- if (refundResult.success) {
828
- this.storageAdapter.removeToken(refundResult.baseUrl);
829
- }
830
- }
831
- const successfulRefunds = refundResults.filter(
832
- (r) => r.status === "fulfilled" && r.value.success
833
- ).length;
834
- if (successfulRefunds > 0) {
835
- this._logTransaction("refund", {
836
- amount: pendingDistribution.length,
837
- mintUrl,
838
- status: "success",
839
- details: `Refunded ${successfulRefunds} of ${pendingDistribution.length} tokens`
840
- });
841
- }
842
- return this._spendInternal({
843
- ...options,
844
- retryCount: (retryCount || 0) + 1
845
- });
846
- }
847
- /**
848
- * Find an alternate mint that the provider accepts
849
- */
850
- async _findAlternateMint(options, balances, units, providerMints) {
851
- const { amount, excludeMints } = options;
852
- const adjustedAmount = Math.ceil(amount) + 2;
853
- const extendedExcludes = [...excludeMints || []];
854
- while (true) {
855
- const { selectedMintUrl } = this._selectMintWithBalance(
856
- balances,
857
- units,
858
- adjustedAmount,
859
- extendedExcludes
1114
+ results.push(
1115
+ ...refundResults.map(
1116
+ (r) => r.status === "fulfilled" ? r.value : { baseUrl: "", success: false }
1117
+ )
1118
+ );
1119
+ if (refundApiKeys) {
1120
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1121
+ const apiKeysToRefund = apiKeyDistribution.filter(
1122
+ (p) => baseUrls.includes(p.baseUrl)
860
1123
  );
861
- if (!selectedMintUrl) break;
862
- if (providerMints.includes(selectedMintUrl)) {
863
- try {
864
- const token = await this.walletAdapter.sendToken(
865
- selectedMintUrl,
866
- adjustedAmount
867
- );
868
- if (options.baseUrl) {
869
- this.storageAdapter.setToken(options.baseUrl, token);
1124
+ for (const apiKeyEntry of apiKeysToRefund) {
1125
+ const apiKeyEntryFull = this.storageAdapter.getApiKey(
1126
+ apiKeyEntry.baseUrl
1127
+ );
1128
+ if (apiKeyEntryFull && this.balanceManager) {
1129
+ const refundResult = await this.balanceManager.refundApiKey({
1130
+ mintUrl,
1131
+ baseUrl: apiKeyEntry.baseUrl,
1132
+ apiKey: apiKeyEntryFull.key
1133
+ });
1134
+ if (refundResult.success) {
1135
+ this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
870
1136
  }
871
- return {
872
- token,
873
- status: "success",
874
- balance: adjustedAmount,
875
- unit: units[selectedMintUrl] || "sat"
876
- };
877
- } catch (error) {
878
- extendedExcludes.push(selectedMintUrl);
1137
+ results.push({
1138
+ baseUrl: apiKeyEntry.baseUrl,
1139
+ success: refundResult.success
1140
+ });
1141
+ } else {
1142
+ results.push({
1143
+ baseUrl: apiKeyEntry.baseUrl,
1144
+ success: false
1145
+ });
879
1146
  }
880
- } else {
881
- extendedExcludes.push(selectedMintUrl);
882
- }
883
- }
884
- return null;
885
- }
886
- /**
887
- * Handle send errors with retry logic for network errors
888
- */
889
- async _handleSendError(error, options, balances, units) {
890
- const errorMsg = error instanceof Error ? error.message : String(error);
891
- const isNetworkError = error instanceof Error && (error.message.includes(
892
- "NetworkError when attempting to fetch resource"
893
- ) || error.message.includes("Failed to fetch") || error.message.includes("Load failed"));
894
- if (isNetworkError) {
895
- const { mintUrl, amount, baseUrl, p2pkPubkey, excludeMints, retryCount } = options;
896
- const extendedExcludes = [...excludeMints || [], mintUrl];
897
- const { selectedMintUrl } = this._selectMintWithBalance(
898
- balances,
899
- units,
900
- Math.ceil(amount),
901
- extendedExcludes
902
- );
903
- if (selectedMintUrl && (retryCount || 0) < Object.keys(balances).length) {
904
- return this._spendInternal({
905
- ...options,
906
- mintUrl: selectedMintUrl,
907
- excludeMints: extendedExcludes,
908
- retryCount: (retryCount || 0) + 1
909
- });
910
1147
  }
911
- throw new MintUnreachableError(mintUrl);
912
1148
  }
913
- return {
914
- token: null,
915
- status: "failed",
916
- balance: 0,
917
- error: `Error generating token: ${errorMsg}`
918
- };
919
- }
920
- /**
921
- * Select a mint with sufficient balance
922
- */
923
- _selectMintWithBalance(balances, units, amount, excludeMints = []) {
924
- for (const mintUrl in balances) {
925
- if (excludeMints.includes(mintUrl)) {
926
- continue;
927
- }
928
- const balance = balances[mintUrl];
929
- const unit = units[mintUrl];
930
- const balanceInSats = unit === "msat" ? balance / 1e3 : balance;
931
- if (balanceInSats >= amount) {
932
- return { selectedMintUrl: mintUrl, selectedMintBalance: balanceInSats };
933
- }
934
- }
935
- return { selectedMintUrl: null, selectedMintBalance: 0 };
1149
+ return results;
936
1150
  }
937
1151
  /**
938
1152
  * Create an insufficient balance error result
@@ -943,7 +1157,7 @@ var CashuSpender = class {
943
1157
  for (const mintUrl in balances) {
944
1158
  const balance = balances[mintUrl];
945
1159
  const unit = units[mintUrl];
946
- const balanceInSats = unit === "msat" ? balance / 1e3 : balance;
1160
+ const balanceInSats = getBalanceInSats(balance, unit);
947
1161
  if (balanceInSats > maxBalance) {
948
1162
  maxBalance = balanceInSats;
949
1163
  maxMintUrl = mintUrl;
@@ -988,10 +1202,22 @@ var CashuSpender = class {
988
1202
 
989
1203
  // wallet/BalanceManager.ts
990
1204
  var BalanceManager = class {
991
- constructor(walletAdapter, storageAdapter) {
1205
+ constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
992
1206
  this.walletAdapter = walletAdapter;
993
1207
  this.storageAdapter = storageAdapter;
1208
+ this.providerRegistry = providerRegistry;
1209
+ if (cashuSpender) {
1210
+ this.cashuSpender = cashuSpender;
1211
+ } else {
1212
+ this.cashuSpender = new CashuSpender(
1213
+ walletAdapter,
1214
+ storageAdapter,
1215
+ providerRegistry,
1216
+ this
1217
+ );
1218
+ }
994
1219
  }
1220
+ cashuSpender;
995
1221
  /**
996
1222
  * Unified refund - handles both NIP-60 and legacy wallet refunds
997
1223
  */
@@ -1026,7 +1252,7 @@ var BalanceManager = class {
1026
1252
  this.storageAdapter.removeToken(baseUrl);
1027
1253
  return { success: true, message: "No balance to refund" };
1028
1254
  }
1029
- const receiveResult = await this.walletAdapter.receiveToken(
1255
+ const receiveResult = await this.cashuSpender.receiveToken(
1030
1256
  fetchResult.token
1031
1257
  );
1032
1258
  const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
@@ -1044,52 +1270,51 @@ var BalanceManager = class {
1044
1270
  }
1045
1271
  }
1046
1272
  /**
1047
- * Top up API key balance with a cashu token
1273
+ * Refund API key balance - convert remaining API key balance to cashu token
1048
1274
  */
1049
- async topUp(options) {
1050
- const { mintUrl, baseUrl, amount, token: providedToken } = options;
1051
- if (!amount || amount <= 0) {
1052
- return { success: false, message: "Invalid top up amount" };
1053
- }
1054
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
1055
- if (!storedToken) {
1056
- return { success: false, message: "No API key available for top up" };
1275
+ async refundApiKey(options) {
1276
+ const { mintUrl, baseUrl, apiKey } = options;
1277
+ if (!apiKey) {
1278
+ return { success: false, message: "No API key to refund" };
1057
1279
  }
1058
- let cashuToken = null;
1059
- let requestId;
1280
+ let fetchResult;
1060
1281
  try {
1061
- cashuToken = await this.walletAdapter.sendToken(mintUrl, amount);
1062
- const topUpResult = await this._postTopUp(
1063
- baseUrl,
1064
- storedToken,
1065
- cashuToken
1066
- );
1067
- requestId = topUpResult.requestId;
1068
- if (!topUpResult.success) {
1069
- await this._recoverFailedTopUp(cashuToken);
1282
+ fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
1283
+ if (!fetchResult.success) {
1070
1284
  return {
1071
1285
  success: false,
1072
- message: topUpResult.error || "Top up failed",
1073
- requestId,
1074
- recoveredToken: true
1286
+ message: fetchResult.error || "API key refund failed",
1287
+ requestId: fetchResult.requestId
1288
+ };
1289
+ }
1290
+ if (!fetchResult.token) {
1291
+ return {
1292
+ success: false,
1293
+ message: "No token received from API key refund",
1294
+ requestId: fetchResult.requestId
1075
1295
  };
1076
1296
  }
1297
+ if (fetchResult.error === "No balance to refund") {
1298
+ return { success: false, message: "No balance to refund" };
1299
+ }
1300
+ const receiveResult = await this.cashuSpender.receiveToken(
1301
+ fetchResult.token
1302
+ );
1303
+ const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
1077
1304
  return {
1078
- success: true,
1079
- toppedUpAmount: amount,
1080
- requestId
1305
+ success: receiveResult.success,
1306
+ refundedAmount: totalAmountMsat,
1307
+ requestId: fetchResult.requestId
1081
1308
  };
1082
1309
  } catch (error) {
1083
- if (cashuToken) {
1084
- await this._recoverFailedTopUp(cashuToken);
1085
- }
1086
- return this._handleTopUpError(error, mintUrl, requestId);
1310
+ console.error("[BalanceManager] API key refund error", error);
1311
+ return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
1087
1312
  }
1088
1313
  }
1089
1314
  /**
1090
- * Fetch refund token from provider API
1315
+ * Fetch refund token from provider API using API key authentication
1091
1316
  */
1092
- async _fetchRefundToken(baseUrl, storedToken) {
1317
+ async _fetchRefundTokenWithApiKey(baseUrl, apiKey) {
1093
1318
  if (!baseUrl) {
1094
1319
  return {
1095
1320
  success: false,
@@ -1106,7 +1331,7 @@ var BalanceManager = class {
1106
1331
  const response = await fetch(url, {
1107
1332
  method: "POST",
1108
1333
  headers: {
1109
- Authorization: `Bearer ${storedToken}`,
1334
+ Authorization: `Bearer ${apiKey}`,
1110
1335
  "Content-Type": "application/json"
1111
1336
  },
1112
1337
  signal: controller.signal
@@ -1115,22 +1340,314 @@ var BalanceManager = class {
1115
1340
  const requestId = response.headers.get("x-routstr-request-id") || void 0;
1116
1341
  if (!response.ok) {
1117
1342
  const errorData = await response.json().catch(() => ({}));
1118
- if (response.status === 400 && errorData?.detail === "No balance to refund") {
1119
- this.storageAdapter.removeToken(baseUrl);
1120
- return {
1121
- success: false,
1122
- requestId,
1123
- error: "No balance to refund"
1124
- };
1125
- }
1126
1343
  return {
1127
1344
  success: false,
1128
1345
  requestId,
1129
- error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
1346
+ error: `API key refund failed: ${errorData?.detail || response.statusText}`
1130
1347
  };
1131
1348
  }
1132
1349
  const data = await response.json();
1133
- console.log("refund rsule", data);
1350
+ return {
1351
+ success: true,
1352
+ token: data.token,
1353
+ requestId
1354
+ };
1355
+ } catch (error) {
1356
+ clearTimeout(timeoutId);
1357
+ console.error(
1358
+ "[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
1359
+ error
1360
+ );
1361
+ if (error instanceof Error) {
1362
+ if (error.name === "AbortError") {
1363
+ return {
1364
+ success: false,
1365
+ error: "Request timed out after 1 minute"
1366
+ };
1367
+ }
1368
+ return {
1369
+ success: false,
1370
+ error: error.message
1371
+ };
1372
+ }
1373
+ return {
1374
+ success: false,
1375
+ error: "Unknown error occurred during API key refund request"
1376
+ };
1377
+ }
1378
+ }
1379
+ /**
1380
+ * Top up API key balance with a cashu token
1381
+ */
1382
+ async topUp(options) {
1383
+ const { mintUrl, baseUrl, amount, token: providedToken } = options;
1384
+ if (!amount || amount <= 0) {
1385
+ return { success: false, message: "Invalid top up amount" };
1386
+ }
1387
+ const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
1388
+ if (!storedToken) {
1389
+ return { success: false, message: "No API key available for top up" };
1390
+ }
1391
+ let cashuToken = null;
1392
+ let requestId;
1393
+ try {
1394
+ const tokenResult = await this.createProviderToken({
1395
+ mintUrl,
1396
+ baseUrl,
1397
+ amount
1398
+ });
1399
+ if (!tokenResult.success || !tokenResult.token) {
1400
+ return {
1401
+ success: false,
1402
+ message: tokenResult.error || "Unable to create top up token"
1403
+ };
1404
+ }
1405
+ cashuToken = tokenResult.token;
1406
+ const topUpResult = await this._postTopUp(
1407
+ baseUrl,
1408
+ storedToken,
1409
+ cashuToken
1410
+ );
1411
+ requestId = topUpResult.requestId;
1412
+ console.log(topUpResult);
1413
+ if (!topUpResult.success) {
1414
+ await this._recoverFailedTopUp(cashuToken);
1415
+ return {
1416
+ success: false,
1417
+ message: topUpResult.error || "Top up failed",
1418
+ requestId,
1419
+ recoveredToken: true
1420
+ };
1421
+ }
1422
+ return {
1423
+ success: true,
1424
+ toppedUpAmount: amount,
1425
+ requestId
1426
+ };
1427
+ } catch (error) {
1428
+ if (cashuToken) {
1429
+ await this._recoverFailedTopUp(cashuToken);
1430
+ }
1431
+ return this._handleTopUpError(error, mintUrl, requestId);
1432
+ }
1433
+ }
1434
+ async createProviderToken(options) {
1435
+ const {
1436
+ mintUrl,
1437
+ baseUrl,
1438
+ amount,
1439
+ retryCount = 0,
1440
+ excludeMints = [],
1441
+ p2pkPubkey
1442
+ } = options;
1443
+ const adjustedAmount = Math.ceil(amount);
1444
+ if (!adjustedAmount || isNaN(adjustedAmount)) {
1445
+ return { success: false, error: "Invalid top up amount" };
1446
+ }
1447
+ const balances = await this.walletAdapter.getBalances();
1448
+ const units = this.walletAdapter.getMintUnits();
1449
+ let totalMintBalance = 0;
1450
+ for (const url in balances) {
1451
+ const unit = units[url];
1452
+ const balanceInSats = getBalanceInSats(balances[url], unit);
1453
+ totalMintBalance += balanceInSats;
1454
+ }
1455
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1456
+ const refundablePending = pendingDistribution.filter((entry) => entry.baseUrl !== baseUrl).reduce((sum, entry) => sum + entry.amount, 0);
1457
+ if (totalMintBalance < adjustedAmount && totalMintBalance + refundablePending >= adjustedAmount && retryCount < 1) {
1458
+ await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
1459
+ return this.createProviderToken({
1460
+ ...options,
1461
+ retryCount: retryCount + 1
1462
+ });
1463
+ }
1464
+ const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
1465
+ let requiredAmount = adjustedAmount;
1466
+ const supportedMintsOnly = providerMints.length > 0;
1467
+ let candidates = this._selectCandidateMints({
1468
+ balances,
1469
+ units,
1470
+ amount: requiredAmount,
1471
+ preferredMintUrl: mintUrl,
1472
+ excludeMints,
1473
+ allowedMints: supportedMintsOnly ? providerMints : void 0
1474
+ });
1475
+ if (candidates.length === 0 && supportedMintsOnly) {
1476
+ requiredAmount += 2;
1477
+ candidates = this._selectCandidateMints({
1478
+ balances,
1479
+ units,
1480
+ amount: requiredAmount,
1481
+ preferredMintUrl: mintUrl,
1482
+ excludeMints
1483
+ });
1484
+ }
1485
+ if (candidates.length === 0) {
1486
+ let maxBalance = 0;
1487
+ let maxMintUrl = "";
1488
+ for (const mintUrl2 in balances) {
1489
+ const balance = balances[mintUrl2];
1490
+ const unit = units[mintUrl2];
1491
+ const balanceInSats = getBalanceInSats(balance, unit);
1492
+ if (balanceInSats > maxBalance) {
1493
+ maxBalance = balanceInSats;
1494
+ maxMintUrl = mintUrl2;
1495
+ }
1496
+ }
1497
+ const error = new InsufficientBalanceError(
1498
+ adjustedAmount,
1499
+ totalMintBalance,
1500
+ maxBalance,
1501
+ maxMintUrl
1502
+ );
1503
+ return { success: false, error: error.message };
1504
+ }
1505
+ let lastError;
1506
+ for (const candidateMint of candidates) {
1507
+ try {
1508
+ const token = await this.walletAdapter.sendToken(
1509
+ candidateMint,
1510
+ requiredAmount,
1511
+ p2pkPubkey
1512
+ );
1513
+ return {
1514
+ success: true,
1515
+ token,
1516
+ selectedMintUrl: candidateMint,
1517
+ amountSpent: requiredAmount
1518
+ };
1519
+ } catch (error) {
1520
+ if (error instanceof Error) {
1521
+ lastError = error.message;
1522
+ if (isNetworkErrorMessage(error.message)) {
1523
+ continue;
1524
+ }
1525
+ }
1526
+ return {
1527
+ success: false,
1528
+ error: lastError || "Failed to create top up token"
1529
+ };
1530
+ }
1531
+ }
1532
+ return {
1533
+ success: false,
1534
+ error: lastError || "All candidate mints failed while creating top up token"
1535
+ };
1536
+ }
1537
+ _selectCandidateMints(options) {
1538
+ const {
1539
+ balances,
1540
+ units,
1541
+ amount,
1542
+ preferredMintUrl,
1543
+ excludeMints,
1544
+ allowedMints
1545
+ } = options;
1546
+ const candidates = [];
1547
+ const { selectedMintUrl: firstMint } = selectMintWithBalance(
1548
+ balances,
1549
+ units,
1550
+ amount,
1551
+ excludeMints
1552
+ );
1553
+ if (firstMint && (!allowedMints || allowedMints.length === 0 || allowedMints.includes(firstMint))) {
1554
+ candidates.push(firstMint);
1555
+ }
1556
+ const canUseMint = (mint) => {
1557
+ if (excludeMints.includes(mint)) return false;
1558
+ if (allowedMints && allowedMints.length > 0 && !allowedMints.includes(mint)) {
1559
+ return false;
1560
+ }
1561
+ const rawBalance = balances[mint] || 0;
1562
+ const unit = units[mint];
1563
+ const balanceInSats = getBalanceInSats(rawBalance, unit);
1564
+ return balanceInSats >= amount;
1565
+ };
1566
+ if (preferredMintUrl && canUseMint(preferredMintUrl) && !candidates.includes(preferredMintUrl)) {
1567
+ candidates.push(preferredMintUrl);
1568
+ }
1569
+ for (const mint in balances) {
1570
+ if (mint === preferredMintUrl || candidates.includes(mint)) continue;
1571
+ if (canUseMint(mint)) {
1572
+ candidates.push(mint);
1573
+ }
1574
+ }
1575
+ return candidates;
1576
+ }
1577
+ async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
1578
+ const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1579
+ const toRefund = pendingDistribution.filter(
1580
+ (pending) => pending.baseUrl !== baseUrl
1581
+ );
1582
+ const refundResults = await Promise.allSettled(
1583
+ toRefund.map(async (pending) => {
1584
+ const token = this.storageAdapter.getToken(pending.baseUrl);
1585
+ if (!token) {
1586
+ return { baseUrl: pending.baseUrl, success: false };
1587
+ }
1588
+ const tokenBalance = await this.getTokenBalance(token, pending.baseUrl);
1589
+ if (tokenBalance.reserved > 0) {
1590
+ return { baseUrl: pending.baseUrl, success: false };
1591
+ }
1592
+ const result = await this.refund({
1593
+ mintUrl,
1594
+ baseUrl: pending.baseUrl,
1595
+ token
1596
+ });
1597
+ return { baseUrl: pending.baseUrl, success: result.success };
1598
+ })
1599
+ );
1600
+ for (const result of refundResults) {
1601
+ if (result.status === "fulfilled" && result.value.success) {
1602
+ this.storageAdapter.removeToken(result.value.baseUrl);
1603
+ }
1604
+ }
1605
+ }
1606
+ /**
1607
+ * Fetch refund token from provider API
1608
+ */
1609
+ async _fetchRefundToken(baseUrl, storedToken) {
1610
+ if (!baseUrl) {
1611
+ return {
1612
+ success: false,
1613
+ error: "No base URL configured"
1614
+ };
1615
+ }
1616
+ const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1617
+ const url = `${normalizedBaseUrl}v1/wallet/refund`;
1618
+ const controller = new AbortController();
1619
+ const timeoutId = setTimeout(() => {
1620
+ controller.abort();
1621
+ }, 6e4);
1622
+ try {
1623
+ const response = await fetch(url, {
1624
+ method: "POST",
1625
+ headers: {
1626
+ Authorization: `Bearer ${storedToken}`,
1627
+ "Content-Type": "application/json"
1628
+ },
1629
+ signal: controller.signal
1630
+ });
1631
+ clearTimeout(timeoutId);
1632
+ const requestId = response.headers.get("x-routstr-request-id") || void 0;
1633
+ if (!response.ok) {
1634
+ const errorData = await response.json().catch(() => ({}));
1635
+ if (response.status === 400 && errorData?.detail === "No balance to refund") {
1636
+ this.storageAdapter.removeToken(baseUrl);
1637
+ return {
1638
+ success: false,
1639
+ requestId,
1640
+ error: "No balance to refund"
1641
+ };
1642
+ }
1643
+ return {
1644
+ success: false,
1645
+ requestId,
1646
+ error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
1647
+ };
1648
+ }
1649
+ const data = await response.json();
1650
+ console.log("refund rsule", data);
1134
1651
  return {
1135
1652
  success: true,
1136
1653
  token: data.token,
@@ -1221,7 +1738,7 @@ var BalanceManager = class {
1221
1738
  */
1222
1739
  async _recoverFailedTopUp(cashuToken) {
1223
1740
  try {
1224
- await this.walletAdapter.receiveToken(cashuToken);
1741
+ await this.cashuSpender.receiveToken(cashuToken);
1225
1742
  } catch (error) {
1226
1743
  console.error(
1227
1744
  "[BalanceManager._recoverFailedTopUp] Failed to recover token",
@@ -1234,9 +1751,7 @@ var BalanceManager = class {
1234
1751
  */
1235
1752
  _handleRefundError(error, mintUrl, requestId) {
1236
1753
  if (error instanceof Error) {
1237
- if (error.message.includes(
1238
- "NetworkError when attempting to fetch resource"
1239
- ) || error.message.includes("Failed to fetch") || error.message.includes("Load failed")) {
1754
+ if (isNetworkErrorMessage(error.message)) {
1240
1755
  return {
1241
1756
  success: false,
1242
1757
  message: `Failed to connect to the mint: ${mintUrl}`,
@@ -1274,25 +1789,29 @@ var BalanceManager = class {
1274
1789
  });
1275
1790
  if (response.ok) {
1276
1791
  const data = await response.json();
1792
+ console.log("TOKENA FASJDFAS", data);
1277
1793
  return {
1278
1794
  amount: data.balance,
1279
1795
  reserved: data.reserved ?? 0,
1280
1796
  unit: "msat",
1281
1797
  apiKey: data.api_key
1282
1798
  };
1799
+ } else {
1800
+ console.log(response.status);
1801
+ const data = await response.json();
1802
+ console.log("FAILED ", data);
1283
1803
  }
1284
- } catch {
1804
+ } catch (error) {
1805
+ console.error("ERRORR IN RESTPONSE", error);
1285
1806
  }
1286
- return { amount: 0, reserved: 0, unit: "sat", apiKey: "" };
1807
+ return { amount: -1, reserved: 0, unit: "sat", apiKey: "" };
1287
1808
  }
1288
1809
  /**
1289
1810
  * Handle topup errors with specific error types
1290
1811
  */
1291
1812
  _handleTopUpError(error, mintUrl, requestId) {
1292
1813
  if (error instanceof Error) {
1293
- if (error.message.includes(
1294
- "NetworkError when attempting to fetch resource"
1295
- ) || error.message.includes("Failed to fetch") || error.message.includes("Load failed")) {
1814
+ if (isNetworkErrorMessage(error.message)) {
1296
1815
  return {
1297
1816
  success: false,
1298
1817
  message: `Failed to connect to the mint: ${mintUrl}`,
@@ -1688,6 +2207,9 @@ function calculateImageTokens(width, height, detail = "auto") {
1688
2207
  const numTiles = tilesWidth * tilesHeight;
1689
2208
  return 85 + 170 * numTiles;
1690
2209
  }
2210
+ function isInsecureHttpUrl(url) {
2211
+ return url.startsWith("http://");
2212
+ }
1691
2213
  var ProviderManager = class {
1692
2214
  constructor(providerRegistry) {
1693
2215
  this.providerRegistry = providerRegistry;
@@ -1729,7 +2251,7 @@ var ProviderManager = class {
1729
2251
  if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl)) {
1730
2252
  continue;
1731
2253
  }
1732
- if (!torMode && isOnionUrl(baseUrl)) {
2254
+ if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
1733
2255
  continue;
1734
2256
  }
1735
2257
  const model = models.find((m) => m.id === modelId);
@@ -1773,7 +2295,8 @@ var ProviderManager = class {
1773
2295
  const torMode = isTorContext();
1774
2296
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1775
2297
  if (disabledProviders.has(baseUrl)) continue;
1776
- if (!torMode && isOnionUrl(baseUrl)) continue;
2298
+ if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
2299
+ continue;
1777
2300
  const model = models.find((m) => m.id === modelId);
1778
2301
  if (!model) continue;
1779
2302
  const cost = model.sats_pricing?.completion ?? 0;
@@ -1796,7 +2319,8 @@ var ProviderManager = class {
1796
2319
  for (const [baseUrl, models] of Object.entries(allModels)) {
1797
2320
  if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
1798
2321
  if (torMode && !baseUrl.includes(".onion")) continue;
1799
- if (!torMode && baseUrl.includes(".onion")) continue;
2322
+ if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
2323
+ continue;
1800
2324
  const match = models.find(
1801
2325
  (model) => this.normalizeModelId(model.id) === normalizedId
1802
2326
  );
@@ -1913,12 +2437,17 @@ var ProviderManager = class {
1913
2437
  };
1914
2438
 
1915
2439
  // client/RoutstrClient.ts
2440
+ var TOPUP_MARGIN = 0.7;
1916
2441
  var RoutstrClient = class {
1917
2442
  constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
1918
2443
  this.walletAdapter = walletAdapter;
1919
2444
  this.storageAdapter = storageAdapter;
1920
2445
  this.providerRegistry = providerRegistry;
1921
- this.balanceManager = new BalanceManager(walletAdapter, storageAdapter);
2446
+ this.balanceManager = new BalanceManager(
2447
+ walletAdapter,
2448
+ storageAdapter,
2449
+ providerRegistry
2450
+ );
1922
2451
  this.cashuSpender = new CashuSpender(
1923
2452
  walletAdapter,
1924
2453
  storageAdapter,
@@ -1936,12 +2465,39 @@ var RoutstrClient = class {
1936
2465
  providerManager;
1937
2466
  alertLevel;
1938
2467
  mode;
2468
+ debugLevel = "WARN";
1939
2469
  /**
1940
2470
  * Get the current client mode
1941
2471
  */
1942
2472
  getMode() {
1943
2473
  return this.mode;
1944
2474
  }
2475
+ getDebugLevel() {
2476
+ return this.debugLevel;
2477
+ }
2478
+ setDebugLevel(level) {
2479
+ this.debugLevel = level;
2480
+ }
2481
+ _log(level, ...args) {
2482
+ const levelPriority = {
2483
+ DEBUG: 0,
2484
+ WARN: 1,
2485
+ ERROR: 2
2486
+ };
2487
+ if (levelPriority[level] >= levelPriority[this.debugLevel]) {
2488
+ switch (level) {
2489
+ case "DEBUG":
2490
+ console.log(...args);
2491
+ break;
2492
+ case "WARN":
2493
+ console.warn(...args);
2494
+ break;
2495
+ case "ERROR":
2496
+ console.error(...args);
2497
+ break;
2498
+ }
2499
+ }
2500
+ }
1945
2501
  /**
1946
2502
  * Get the CashuSpender instance
1947
2503
  */
@@ -2005,6 +2561,7 @@ var RoutstrClient = class {
2005
2561
  amount: requiredSats,
2006
2562
  baseUrl
2007
2563
  });
2564
+ this._log("DEBUG", token, baseUrl);
2008
2565
  let requestBody = body;
2009
2566
  if (body && typeof body === "object") {
2010
2567
  const bodyObj = body;
@@ -2027,9 +2584,11 @@ var RoutstrClient = class {
2027
2584
  selectedModel
2028
2585
  });
2029
2586
  const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
2587
+ const baseUrlUsed = response.baseUrl || baseUrl;
2588
+ const tokenUsed = response.token || token;
2030
2589
  await this._handlePostResponseBalanceUpdate({
2031
- token,
2032
- baseUrl,
2590
+ token: tokenUsed,
2591
+ baseUrl: baseUrlUsed,
2033
2592
  initialTokenBalance: tokenBalanceInSats,
2034
2593
  response
2035
2594
  });
@@ -2164,19 +2723,34 @@ var RoutstrClient = class {
2164
2723
  const { path, method, body, baseUrl, token, headers } = params;
2165
2724
  try {
2166
2725
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
2726
+ if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
2167
2727
  const response = await fetch(url, {
2168
2728
  method,
2169
2729
  headers,
2170
2730
  body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
2171
2731
  });
2732
+ if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
2172
2733
  response.baseUrl = baseUrl;
2734
+ response.token = token;
2173
2735
  if (!response.ok) {
2174
- return await this._handleErrorResponse(response, params, token);
2736
+ const requestId = response.headers.get("x-routstr-request-id") || void 0;
2737
+ return await this._handleErrorResponse(
2738
+ params,
2739
+ token,
2740
+ response.status,
2741
+ requestId,
2742
+ this.mode === "xcashu" ? response.headers.get("x-cashu") ?? void 0 : void 0
2743
+ );
2175
2744
  }
2176
2745
  return response;
2177
2746
  } catch (error) {
2178
2747
  if (this._isNetworkError(error?.message || "")) {
2179
- return await this._handleNetworkError(error, params);
2748
+ return await this._handleErrorResponse(
2749
+ params,
2750
+ token,
2751
+ -1
2752
+ // just for Network Error to skip all statuses
2753
+ );
2180
2754
  }
2181
2755
  throw error;
2182
2756
  }
@@ -2184,250 +2758,239 @@ var RoutstrClient = class {
2184
2758
  /**
2185
2759
  * Handle error responses with failover
2186
2760
  */
2187
- async _handleErrorResponse(response, params, token) {
2761
+ async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken) {
2188
2762
  const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
2189
- const status = response.status;
2190
- if (this.mode === "apikeys") {
2191
- console.log("error ;", status);
2192
- if (status === 401 || status === 403) {
2193
- const parentApiKey = this.storageAdapter.getApiKey(baseUrl);
2194
- if (parentApiKey) {
2195
- try {
2196
- const childKeyResult = await this._createChildKey(
2197
- baseUrl,
2198
- parentApiKey
2763
+ let tryNextProvider = false;
2764
+ this._log(
2765
+ "DEBUG",
2766
+ `[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}`
2767
+ );
2768
+ this._log(
2769
+ "DEBUG",
2770
+ `[RoutstrClient] _handleErrorResponse: Attempting to receive/restore token for ${baseUrl}`
2771
+ );
2772
+ if (params.token.startsWith("cashu")) {
2773
+ const tryReceiveTokenResult = await this.cashuSpender.receiveToken(
2774
+ params.token
2775
+ );
2776
+ if (tryReceiveTokenResult.success) {
2777
+ this._log(
2778
+ "DEBUG",
2779
+ `[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
2780
+ );
2781
+ tryNextProvider = true;
2782
+ if (this.mode === "lazyrefund")
2783
+ this.storageAdapter.removeToken(baseUrl);
2784
+ } else {
2785
+ this._log(
2786
+ "DEBUG",
2787
+ `[RoutstrClient] _handleErrorResponse: Failed to receive token. `
2788
+ );
2789
+ }
2790
+ }
2791
+ if (this.mode === "xcashu") {
2792
+ if (xCashuRefundToken) {
2793
+ this._log(
2794
+ "DEBUG",
2795
+ `[RoutstrClient] _handleErrorResponse: Attempting to receive xcashu refund token, preview=${xCashuRefundToken.substring(0, 20)}...`
2796
+ );
2797
+ try {
2798
+ const receiveResult = await this.cashuSpender.receiveToken(xCashuRefundToken);
2799
+ if (receiveResult.success) {
2800
+ this._log(
2801
+ "DEBUG",
2802
+ `[RoutstrClient] _handleErrorResponse: xcashu refund received, amount=${receiveResult.amount}`
2199
2803
  );
2200
- this.storageAdapter.setChildKey(
2804
+ tryNextProvider = true;
2805
+ } else
2806
+ throw new ProviderError(
2201
2807
  baseUrl,
2202
- childKeyResult.childKey,
2203
- childKeyResult.balance,
2204
- childKeyResult.validityDate,
2205
- childKeyResult.balanceLimit
2808
+ status,
2809
+ "xcashu refund failed",
2810
+ requestId
2206
2811
  );
2207
- return this._makeRequest({
2208
- ...params,
2209
- token: childKeyResult.childKey,
2210
- headers: this._withAuthHeader(
2211
- params.baseHeaders,
2212
- childKeyResult.childKey
2213
- )
2214
- });
2215
- } catch (e) {
2216
- }
2217
- }
2218
- } else if (status === 402) {
2219
- const parentApiKey = this.storageAdapter.getApiKey(baseUrl);
2220
- if (parentApiKey) {
2221
- const topupResult = await this.balanceManager.topUp({
2222
- mintUrl,
2812
+ } catch (error) {
2813
+ this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
2814
+ throw new ProviderError(
2223
2815
  baseUrl,
2224
- amount: params.requiredSats * 3,
2225
- token: parentApiKey
2226
- });
2227
- console.log(topupResult);
2228
- return this._makeRequest({
2229
- ...params,
2230
- token: params.token,
2231
- headers: this._withAuthHeader(params.baseHeaders, params.token)
2232
- });
2816
+ status,
2817
+ "[xcashu] Failed to receive refund token",
2818
+ requestId
2819
+ );
2233
2820
  }
2821
+ } else {
2822
+ if (!tryNextProvider)
2823
+ throw new ProviderError(
2824
+ baseUrl,
2825
+ status,
2826
+ "[xcashu] Failed to receive refund token",
2827
+ requestId
2828
+ );
2234
2829
  }
2235
- throw new ProviderError(baseUrl, status, await response.text());
2236
2830
  }
2237
- await this.balanceManager.refund({
2238
- mintUrl,
2239
- baseUrl,
2240
- token
2241
- });
2242
- this.providerManager.markFailed(baseUrl);
2243
- this.storageAdapter.removeToken(baseUrl);
2244
- if (status === 401 || status === 403 || status === 402 || status === 413 || status === 400 || status === 500 || status === 502) {
2245
- if (!selectedModel) {
2246
- throw new ProviderError(baseUrl, status, await response.text());
2247
- }
2248
- const nextProvider = this.providerManager.findNextBestProvider(
2249
- selectedModel.id,
2250
- baseUrl
2831
+ if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
2832
+ const topupResult = await this.balanceManager.topUp({
2833
+ mintUrl,
2834
+ baseUrl,
2835
+ amount: params.requiredSats * TOPUP_MARGIN,
2836
+ token: params.token
2837
+ });
2838
+ this._log(
2839
+ "DEBUG",
2840
+ `[RoutstrClient] _handleErrorResponse: Topup result for ${baseUrl}: success=${topupResult.success}, message=${topupResult.message}`
2251
2841
  );
2252
- if (nextProvider) {
2253
- const newModel = await this.providerManager.getModelForProvider(
2254
- nextProvider,
2255
- selectedModel.id
2256
- ) ?? selectedModel;
2257
- const messagesForPricing = Array.isArray(
2258
- body?.messages
2259
- ) ? body.messages : [];
2260
- const newRequiredSats = this.providerManager.getRequiredSatsForModel(
2261
- newModel,
2262
- messagesForPricing,
2263
- params.maxTokens
2842
+ if (!topupResult.success) {
2843
+ const message = topupResult.message || "";
2844
+ if (message.includes("Insufficient balance")) {
2845
+ const needMatch = message.match(/need (\d+)/);
2846
+ const haveMatch = message.match(/have (\d+)/);
2847
+ const required = needMatch ? parseInt(needMatch[1], 10) : params.requiredSats;
2848
+ const available = haveMatch ? parseInt(haveMatch[1], 10) : 0;
2849
+ this._log(
2850
+ "DEBUG",
2851
+ `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
2852
+ );
2853
+ throw new InsufficientBalanceError(required, available);
2854
+ } else {
2855
+ this._log(
2856
+ "DEBUG",
2857
+ `[RoutstrClient] _handleErrorResponse: Topup failed with non-insufficient-balance error, will try next provider`
2858
+ );
2859
+ tryNextProvider = true;
2860
+ }
2861
+ } else {
2862
+ this._log(
2863
+ "DEBUG",
2864
+ `[RoutstrClient] _handleErrorResponse: Topup successful, will retry with new token`
2264
2865
  );
2265
- const spendResult = await this.cashuSpender.spend({
2266
- mintUrl,
2267
- amount: newRequiredSats,
2268
- baseUrl: nextProvider,
2269
- reuseToken: true
2866
+ }
2867
+ if (!tryNextProvider)
2868
+ return this._makeRequest({
2869
+ ...params,
2870
+ token: params.token,
2871
+ headers: this._withAuthHeader(params.baseHeaders, params.token)
2270
2872
  });
2271
- if (spendResult.status === "failed" || !spendResult.token) {
2272
- if (spendResult.errorDetails) {
2273
- throw new InsufficientBalanceError(
2274
- spendResult.errorDetails.required,
2275
- spendResult.errorDetails.available,
2276
- spendResult.errorDetails.maxMintBalance,
2277
- spendResult.errorDetails.maxMintUrl
2873
+ }
2874
+ if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
2875
+ this._log(
2876
+ "DEBUG",
2877
+ `[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
2878
+ );
2879
+ if (this.mode === "lazyrefund") {
2880
+ try {
2881
+ const refundResult = await this.balanceManager.refund({
2882
+ mintUrl,
2883
+ baseUrl,
2884
+ token: params.token
2885
+ });
2886
+ this._log(
2887
+ "DEBUG",
2888
+ `[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
2889
+ );
2890
+ if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
2891
+ else
2892
+ throw new ProviderError(
2893
+ baseUrl,
2894
+ status,
2895
+ "refund failed",
2896
+ requestId
2278
2897
  );
2279
- }
2280
- throw new Error(
2281
- spendResult.error || `Insufficient balance for ${nextProvider}`
2898
+ } catch (error) {
2899
+ throw new ProviderError(
2900
+ baseUrl,
2901
+ status,
2902
+ "Failed to refund token",
2903
+ requestId
2282
2904
  );
2283
2905
  }
2284
- return this._makeRequest({
2285
- ...params,
2286
- path,
2287
- method,
2288
- body,
2289
- baseUrl: nextProvider,
2290
- selectedModel: newModel,
2291
- token: spendResult.token,
2292
- requiredSats: newRequiredSats,
2293
- headers: this._withAuthHeader(params.baseHeaders, spendResult.token)
2906
+ } else if (this.mode === "apikeys") {
2907
+ this._log(
2908
+ "DEBUG",
2909
+ `[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
2910
+ );
2911
+ const initialBalance = await this.balanceManager.getTokenBalance(
2912
+ token,
2913
+ baseUrl
2914
+ );
2915
+ this._log(
2916
+ "DEBUG",
2917
+ `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${initialBalance.amount}`
2918
+ );
2919
+ const refundResult = await this.balanceManager.refundApiKey({
2920
+ mintUrl,
2921
+ baseUrl,
2922
+ apiKey: token
2294
2923
  });
2924
+ this._log(
2925
+ "DEBUG",
2926
+ `[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
2927
+ );
2928
+ if (!refundResult.success && initialBalance.amount > 0) {
2929
+ throw new ProviderError(
2930
+ baseUrl,
2931
+ status,
2932
+ refundResult.message ?? "Unknown error"
2933
+ );
2934
+ } else {
2935
+ this.storageAdapter.removeApiKey(baseUrl);
2936
+ }
2295
2937
  }
2296
2938
  }
2297
- throw new ProviderError(baseUrl, status, await response.text());
2298
- }
2299
- /**
2300
- * Handle network errors with failover
2301
- */
2302
- async _handleNetworkError(error, params) {
2303
- const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
2304
- await this.balanceManager.refund({
2305
- mintUrl,
2306
- baseUrl,
2307
- token: params.token
2308
- });
2309
2939
  this.providerManager.markFailed(baseUrl);
2940
+ this._log(
2941
+ "DEBUG",
2942
+ `[RoutstrClient] _handleErrorResponse: Marked provider ${baseUrl} as failed`
2943
+ );
2310
2944
  if (!selectedModel) {
2311
- throw error;
2945
+ throw new ProviderError(
2946
+ baseUrl,
2947
+ status,
2948
+ "Funny, no selected model. HMM. "
2949
+ );
2312
2950
  }
2313
2951
  const nextProvider = this.providerManager.findNextBestProvider(
2314
2952
  selectedModel.id,
2315
2953
  baseUrl
2316
2954
  );
2317
- if (!nextProvider) {
2318
- throw new FailoverError(baseUrl, Array.from(this.providerManager));
2319
- }
2320
- const newModel = await this.providerManager.getModelForProvider(
2321
- nextProvider,
2322
- selectedModel.id
2323
- ) ?? selectedModel;
2324
- const messagesForPricing = Array.isArray(
2325
- body?.messages
2326
- ) ? body.messages : [];
2327
- const newRequiredSats = this.providerManager.getRequiredSatsForModel(
2328
- newModel,
2329
- messagesForPricing,
2330
- params.maxTokens
2331
- );
2332
- const spendResult = await this.cashuSpender.spend({
2333
- mintUrl,
2334
- amount: newRequiredSats,
2335
- baseUrl: nextProvider,
2336
- reuseToken: true
2337
- });
2338
- if (spendResult.status === "failed" || !spendResult.token) {
2339
- if (spendResult.errorDetails) {
2340
- throw new InsufficientBalanceError(
2341
- spendResult.errorDetails.required,
2342
- spendResult.errorDetails.available,
2343
- spendResult.errorDetails.maxMintBalance,
2344
- spendResult.errorDetails.maxMintUrl
2345
- );
2346
- }
2347
- throw new Error(
2348
- spendResult.error || `Insufficient balance for ${nextProvider}`
2955
+ if (nextProvider) {
2956
+ this._log(
2957
+ "DEBUG",
2958
+ `[RoutstrClient] _handleErrorResponse: Failing over to next provider: ${nextProvider}, model: ${selectedModel.id}`
2349
2959
  );
2960
+ const newModel = await this.providerManager.getModelForProvider(
2961
+ nextProvider,
2962
+ selectedModel.id
2963
+ ) ?? selectedModel;
2964
+ const messagesForPricing = Array.isArray(
2965
+ body?.messages
2966
+ ) ? body.messages : [];
2967
+ const newRequiredSats = this.providerManager.getRequiredSatsForModel(
2968
+ newModel,
2969
+ messagesForPricing,
2970
+ params.maxTokens
2971
+ );
2972
+ this._log(
2973
+ "DEBUG",
2974
+ `[RoutstrClient] _handleErrorResponse: Creating new token for failover provider ${nextProvider}, required sats: ${newRequiredSats}`
2975
+ );
2976
+ const spendResult = await this._spendToken({
2977
+ mintUrl,
2978
+ amount: newRequiredSats,
2979
+ baseUrl: nextProvider
2980
+ });
2981
+ return this._makeRequest({
2982
+ ...params,
2983
+ path,
2984
+ method,
2985
+ body,
2986
+ baseUrl: nextProvider,
2987
+ selectedModel: newModel,
2988
+ token: spendResult.token,
2989
+ requiredSats: newRequiredSats,
2990
+ headers: this._withAuthHeader(params.baseHeaders, spendResult.token)
2991
+ });
2350
2992
  }
2351
- return this._makeRequest({
2352
- ...params,
2353
- path,
2354
- method,
2355
- body,
2356
- baseUrl: nextProvider,
2357
- selectedModel: newModel,
2358
- token: spendResult.token,
2359
- requiredSats: newRequiredSats,
2360
- headers: this._withAuthHeader(params.baseHeaders, spendResult.token)
2361
- });
2362
- }
2363
- /**
2364
- * Handle post-response refund and balance updates
2365
- */
2366
- async _handlePostResponseRefund(params) {
2367
- const {
2368
- mintUrl,
2369
- baseUrl,
2370
- tokenBalance,
2371
- tokenBalanceUnit,
2372
- initialBalance,
2373
- selectedModel,
2374
- streamingResult,
2375
- callbacks
2376
- } = params;
2377
- const tokenBalanceInSats = tokenBalanceUnit === "msat" ? tokenBalance / 1e3 : tokenBalance;
2378
- const estimatedCosts = this._getEstimatedCosts(
2379
- selectedModel,
2380
- streamingResult
2381
- );
2382
- const refundResult = await this.balanceManager.refund({
2383
- mintUrl,
2384
- baseUrl
2385
- });
2386
- if (refundResult.success) {
2387
- refundResult.refundedAmount !== void 0 ? refundResult.refundedAmount / 1e3 : 0;
2388
- }
2389
- let satsSpent;
2390
- if (refundResult.success) {
2391
- if (refundResult.refundedAmount !== void 0) {
2392
- satsSpent = tokenBalanceInSats - refundResult.refundedAmount / 1e3;
2393
- } else if (refundResult.message?.includes("No API key to refund")) {
2394
- satsSpent = 0;
2395
- } else {
2396
- satsSpent = tokenBalanceInSats;
2397
- }
2398
- const newBalance = initialBalance - satsSpent;
2399
- callbacks.onBalanceUpdate(newBalance);
2400
- } else {
2401
- if (refundResult.message?.includes("Refund request failed with status 401")) {
2402
- this.storageAdapter.removeToken(baseUrl);
2403
- }
2404
- satsSpent = tokenBalanceInSats;
2405
- }
2406
- const netCosts = satsSpent - estimatedCosts;
2407
- const overchargeThreshold = tokenBalanceUnit === "msat" ? 0.05 : 1;
2408
- if (netCosts > overchargeThreshold) {
2409
- if (this.alertLevel === "max") {
2410
- callbacks.onMessageAppend({
2411
- role: "system",
2412
- content: `ATTENTION: Provider may be overcharging. Estimated: ${estimatedCosts.toFixed(
2413
- tokenBalanceUnit === "msat" ? 3 : 0
2414
- )}, Actual: ${satsSpent.toFixed(
2415
- tokenBalanceUnit === "msat" ? 3 : 0
2416
- )}`
2417
- });
2418
- }
2419
- }
2420
- const newTransaction = {
2421
- type: "spent",
2422
- amount: satsSpent,
2423
- timestamp: Date.now(),
2424
- status: "success",
2425
- model: selectedModel.id,
2426
- message: "Tokens spent",
2427
- balance: initialBalance - satsSpent
2428
- };
2429
- callbacks.onTransactionUpdate(newTransaction);
2430
- return satsSpent;
2993
+ throw new FailoverError(baseUrl, Array.from(this.providerManager));
2431
2994
  }
2432
2995
  /**
2433
2996
  * Handle post-response balance update for all modes
@@ -2439,10 +3002,10 @@ var RoutstrClient = class {
2439
3002
  const refundToken = response.headers.get("x-cashu") ?? void 0;
2440
3003
  if (refundToken) {
2441
3004
  try {
2442
- const receiveResult = await this.walletAdapter.receiveToken(refundToken);
3005
+ const receiveResult = await this.cashuSpender.receiveToken(refundToken);
2443
3006
  satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
2444
3007
  } catch (error) {
2445
- console.error("[xcashu] Failed to receive refund token:", error);
3008
+ this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
2446
3009
  }
2447
3010
  }
2448
3011
  } else if (this.mode === "lazyrefund") {
@@ -2455,13 +3018,24 @@ var RoutstrClient = class {
2455
3018
  satsSpent = initialTokenBalance - latestTokenBalance;
2456
3019
  } else if (this.mode === "apikeys") {
2457
3020
  try {
2458
- const latestBalanceInfo = await this._getApiKeyBalance(baseUrl, token);
2459
- console.log("LATEST BANAL", latestBalanceInfo);
3021
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
3022
+ token,
3023
+ baseUrl
3024
+ );
3025
+ this._log(
3026
+ "DEBUG",
3027
+ "LATEST Balance",
3028
+ latestBalanceInfo.amount,
3029
+ latestBalanceInfo.reserved,
3030
+ latestBalanceInfo.apiKey,
3031
+ baseUrl
3032
+ );
2460
3033
  const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
2461
3034
  this.storageAdapter.updateChildKeyBalance(baseUrl, latestTokenBalance);
3035
+ this.storageAdapter.updateApiKeyBalance(baseUrl, latestTokenBalance);
2462
3036
  satsSpent = initialTokenBalance - latestTokenBalance;
2463
3037
  } catch (e) {
2464
- console.warn("Could not get updated API key balance:", e);
3038
+ this._log("WARN", "Could not get updated API key balance:", e);
2465
3039
  satsSpent = fallbackSatsSpent ?? initialTokenBalance;
2466
3040
  }
2467
3041
  }
@@ -2542,28 +3116,6 @@ var RoutstrClient = class {
2542
3116
  validityDate: data.validity_date
2543
3117
  };
2544
3118
  }
2545
- /**
2546
- * Get balance for an API key from the provider
2547
- */
2548
- async _getApiKeyBalance(baseUrl, apiKey) {
2549
- try {
2550
- const response = await fetch(`${baseUrl}v1/wallet/info`, {
2551
- headers: {
2552
- Authorization: `Bearer ${apiKey}`
2553
- }
2554
- });
2555
- if (response.ok) {
2556
- const data = await response.json();
2557
- console.log(data);
2558
- return {
2559
- amount: data.balance,
2560
- unit: "msat"
2561
- };
2562
- }
2563
- } catch {
2564
- }
2565
- return { amount: 0, unit: "sat" };
2566
- }
2567
3119
  /**
2568
3120
  * Calculate estimated costs from usage
2569
3121
  */
@@ -2581,7 +3133,7 @@ var RoutstrClient = class {
2581
3133
  * Get pending cashu token amount
2582
3134
  */
2583
3135
  _getPendingCashuTokenAmount() {
2584
- const distribution = this.storageAdapter.getPendingTokenDistribution();
3136
+ const distribution = this.storageAdapter.getCachedTokenDistribution();
2585
3137
  return distribution.reduce((total, item) => total + item.amount, 0);
2586
3138
  }
2587
3139
  /**
@@ -2594,9 +3146,14 @@ var RoutstrClient = class {
2594
3146
  * Handle errors and notify callbacks
2595
3147
  */
2596
3148
  _handleError(error, callbacks) {
2597
- console.error("RoutstrClient error:", error);
3149
+ this._log("ERROR", "[RoutstrClient] _handleError: Error occurred", error);
2598
3150
  if (error instanceof Error) {
2599
- const modifiedErrorMsg = error.message.includes("Error in input stream") || error.message.includes("Load failed") ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
3151
+ const isStreamError = error.message.includes("Error in input stream") || error.message.includes("Load failed");
3152
+ const modifiedErrorMsg = isStreamError ? "AI stream was cut off, turn on Keep Active or please try again" : error.message;
3153
+ this._log(
3154
+ "ERROR",
3155
+ `[RoutstrClient] _handleError: Error type=${error.constructor.name}, message=${modifiedErrorMsg}, isStreamError=${isStreamError}`
3156
+ );
2600
3157
  callbacks.onMessageAppend({
2601
3158
  role: "system",
2602
3159
  content: "Uncaught Error: " + modifiedErrorMsg + (this.alertLevel === "max" ? " | " + error.stack : "")
@@ -2623,113 +3180,127 @@ var RoutstrClient = class {
2623
3180
  */
2624
3181
  async _spendToken(params) {
2625
3182
  const { mintUrl, amount, baseUrl } = params;
3183
+ this._log(
3184
+ "DEBUG",
3185
+ `[RoutstrClient] _spendToken: mode=${this.mode}, amount=${amount}, baseUrl=${baseUrl}, mintUrl=${mintUrl}`
3186
+ );
2626
3187
  if (this.mode === "apikeys") {
2627
3188
  let parentApiKey = this.storageAdapter.getApiKey(baseUrl);
2628
3189
  if (!parentApiKey) {
3190
+ this._log(
3191
+ "DEBUG",
3192
+ `[RoutstrClient] _spendToken: No existing API key for ${baseUrl}, creating new one via Cashu`
3193
+ );
2629
3194
  const spendResult2 = await this.cashuSpender.spend({
2630
3195
  mintUrl,
2631
- amount: amount * 3,
3196
+ amount: amount * TOPUP_MARGIN,
2632
3197
  baseUrl: "",
2633
3198
  reuseToken: false
2634
3199
  });
2635
- if (spendResult2.status === "failed" || !spendResult2.token) {
2636
- const errorMsg = spendResult2.error || `Insufficient balance. Need ${amount} sats.`;
2637
- if (this._isNetworkError(errorMsg)) {
2638
- throw new Error(
2639
- `Your mint ${mintUrl} is unreachable or is blocking your IP. Please try again later or switch mints.`
3200
+ if (!spendResult2.token) {
3201
+ this._log(
3202
+ "ERROR",
3203
+ `[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error:`,
3204
+ spendResult2.error
3205
+ );
3206
+ throw new Error(
3207
+ `[RoutstrClient] _spendToken: Failed to create Cashu token for API key creation, error: ${spendResult2.error}`
3208
+ );
3209
+ } else {
3210
+ this._log(
3211
+ "DEBUG",
3212
+ `[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult2.token}`
3213
+ );
3214
+ }
3215
+ this._log(
3216
+ "DEBUG",
3217
+ `[RoutstrClient] _spendToken: Created API key for ${baseUrl}, key preview: ${spendResult2.token}, balance: ${spendResult2.balance}`
3218
+ );
3219
+ try {
3220
+ this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
3221
+ } catch (error) {
3222
+ if (error instanceof Error && error.message.includes("ApiKey already exists")) {
3223
+ const tryReceiveTokenResult = await this.cashuSpender.receiveToken(
3224
+ spendResult2.token
2640
3225
  );
2641
- }
2642
- if (spendResult2.errorDetails) {
2643
- throw new InsufficientBalanceError(
2644
- spendResult2.errorDetails.required,
2645
- spendResult2.errorDetails.available,
2646
- spendResult2.errorDetails.maxMintBalance,
2647
- spendResult2.errorDetails.maxMintUrl
3226
+ if (tryReceiveTokenResult.success) {
3227
+ this._log(
3228
+ "DEBUG",
3229
+ `[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
3230
+ );
3231
+ } else {
3232
+ this._log(
3233
+ "DEBUG",
3234
+ `[RoutstrClient] _handleErrorResponse: Token restore failed or not needed`
3235
+ );
3236
+ }
3237
+ this._log(
3238
+ "DEBUG",
3239
+ `[RoutstrClient] _spendToken: API key already exists for ${baseUrl}, using existing key`
2648
3240
  );
3241
+ } else {
3242
+ throw error;
2649
3243
  }
2650
- throw new Error(errorMsg);
2651
3244
  }
2652
- const apiKeyCreated = await this.balanceManager.getTokenBalance(
2653
- spendResult2.token,
2654
- baseUrl
3245
+ parentApiKey = this.storageAdapter.getApiKey(baseUrl);
3246
+ } else {
3247
+ this._log(
3248
+ "DEBUG",
3249
+ `[RoutstrClient] _spendToken: Using existing API key for ${baseUrl}, key preview: ${parentApiKey.key}`
2655
3250
  );
2656
- parentApiKey = apiKeyCreated.apiKey;
2657
- this.storageAdapter.setApiKey(baseUrl, parentApiKey);
2658
- }
2659
- let childKeyEntry = this.storageAdapter.getChildKey(baseUrl);
2660
- if (!childKeyEntry) {
2661
- try {
2662
- const childKeyResult = await this._createChildKey(
2663
- baseUrl,
2664
- parentApiKey
2665
- );
2666
- this.storageAdapter.setChildKey(
2667
- baseUrl,
2668
- childKeyResult.childKey,
2669
- childKeyResult.balance,
2670
- childKeyResult.validityDate,
2671
- childKeyResult.balanceLimit
2672
- );
2673
- childKeyEntry = {
2674
- parentBaseUrl: baseUrl,
2675
- childKey: childKeyResult.childKey,
2676
- balance: childKeyResult.balance,
2677
- balanceLimit: childKeyResult.balanceLimit,
2678
- validityDate: childKeyResult.validityDate,
2679
- createdAt: Date.now()
2680
- };
2681
- } catch (e) {
2682
- console.warn("Could not create child key, using parent key:", e);
2683
- childKeyEntry = {
2684
- parentBaseUrl: baseUrl,
2685
- childKey: parentApiKey,
2686
- balance: 0,
2687
- createdAt: Date.now()
2688
- };
2689
- }
2690
3251
  }
2691
- let tokenBalance = childKeyEntry.balance;
3252
+ let tokenBalance = 0;
2692
3253
  let tokenBalanceUnit = "sat";
2693
- if (tokenBalance === 0) {
3254
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
3255
+ const distributionForBaseUrl = apiKeyDistribution.find(
3256
+ (d) => d.baseUrl === baseUrl
3257
+ );
3258
+ if (distributionForBaseUrl) {
3259
+ tokenBalance = distributionForBaseUrl.amount;
3260
+ }
3261
+ if (tokenBalance === 0 && parentApiKey) {
2694
3262
  try {
2695
- const balanceInfo = await this._getApiKeyBalance(
2696
- baseUrl,
2697
- childKeyEntry.childKey
3263
+ const balanceInfo = await this.balanceManager.getTokenBalance(
3264
+ parentApiKey.key,
3265
+ baseUrl
2698
3266
  );
2699
3267
  tokenBalance = balanceInfo.amount;
2700
3268
  tokenBalanceUnit = balanceInfo.unit;
2701
3269
  } catch (e) {
2702
- console.warn("Could not get initial API key balance:", e);
3270
+ this._log("WARN", "Could not get initial API key balance:", e);
2703
3271
  }
2704
3272
  }
3273
+ this._log(
3274
+ "DEBUG",
3275
+ `[RoutstrClient] _spendToken: Returning token with balance=${tokenBalance} ${tokenBalanceUnit}`
3276
+ );
2705
3277
  return {
2706
- token: childKeyEntry.childKey,
3278
+ token: parentApiKey?.key ?? "",
2707
3279
  tokenBalance,
2708
3280
  tokenBalanceUnit
2709
3281
  };
2710
3282
  }
3283
+ this._log(
3284
+ "DEBUG",
3285
+ `[RoutstrClient] _spendToken: Calling CashuSpender.spend for amount=${amount}, mintUrl=${mintUrl}, mode=${this.mode}`
3286
+ );
2711
3287
  const spendResult = await this.cashuSpender.spend({
2712
3288
  mintUrl,
2713
3289
  amount,
2714
- baseUrl,
2715
- reuseToken: true
3290
+ baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
3291
+ reuseToken: this.mode === "lazyrefund"
2716
3292
  });
2717
- if (spendResult.status === "failed" || !spendResult.token) {
2718
- const errorMsg = spendResult.error || `Insufficient balance. Need ${amount} sats.`;
2719
- if (this._isNetworkError(errorMsg)) {
2720
- throw new Error(
2721
- `Your mint ${mintUrl} is unreachable or is blocking your IP. Please try again later or switch mints.`
2722
- );
2723
- }
2724
- if (spendResult.errorDetails) {
2725
- throw new InsufficientBalanceError(
2726
- spendResult.errorDetails.required,
2727
- spendResult.errorDetails.available,
2728
- spendResult.errorDetails.maxMintBalance,
2729
- spendResult.errorDetails.maxMintUrl
2730
- );
2731
- }
2732
- throw new Error(errorMsg);
3293
+ if (!spendResult.token) {
3294
+ this._log(
3295
+ "ERROR",
3296
+ `[RoutstrClient] _spendToken: CashuSpender.spend failed, error:`,
3297
+ spendResult.error
3298
+ );
3299
+ } else {
3300
+ this._log(
3301
+ "DEBUG",
3302
+ `[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
3303
+ );
2733
3304
  }
2734
3305
  return {
2735
3306
  token: spendResult.token,
@@ -2771,7 +3342,7 @@ var isQuotaExceeded = (error) => {
2771
3342
  };
2772
3343
  var NON_CRITICAL_KEYS = /* @__PURE__ */ new Set(["modelsFromAllProviders"]);
2773
3344
  var localStorageDriver = {
2774
- getItem(key, defaultValue) {
3345
+ async getItem(key, defaultValue) {
2775
3346
  if (!canUseLocalStorage()) return defaultValue;
2776
3347
  try {
2777
3348
  const item = window.localStorage.getItem(key);
@@ -2799,7 +3370,7 @@ var localStorageDriver = {
2799
3370
  return defaultValue;
2800
3371
  }
2801
3372
  },
2802
- setItem(key, value) {
3373
+ async setItem(key, value) {
2803
3374
  if (!canUseLocalStorage()) return;
2804
3375
  try {
2805
3376
  window.localStorage.setItem(key, JSON.stringify(value));
@@ -2829,7 +3400,7 @@ var localStorageDriver = {
2829
3400
  console.error(`Error storing item with key "${key}":`, error);
2830
3401
  }
2831
3402
  },
2832
- removeItem(key) {
3403
+ async removeItem(key) {
2833
3404
  if (!canUseLocalStorage()) return;
2834
3405
  try {
2835
3406
  window.localStorage.removeItem(key);
@@ -2848,7 +3419,7 @@ var createMemoryDriver = (seed) => {
2848
3419
  }
2849
3420
  }
2850
3421
  return {
2851
- getItem(key, defaultValue) {
3422
+ async getItem(key, defaultValue) {
2852
3423
  const item = store.get(key);
2853
3424
  if (item === void 0) return defaultValue;
2854
3425
  try {
@@ -2860,17 +3431,25 @@ var createMemoryDriver = (seed) => {
2860
3431
  throw parseError;
2861
3432
  }
2862
3433
  },
2863
- setItem(key, value) {
3434
+ async setItem(key, value) {
2864
3435
  store.set(key, JSON.stringify(value));
2865
3436
  },
2866
- removeItem(key) {
3437
+ async removeItem(key) {
2867
3438
  store.delete(key);
2868
3439
  }
2869
3440
  };
2870
3441
  };
2871
3442
 
2872
3443
  // storage/drivers/sqlite.ts
3444
+ var isBun = () => {
3445
+ return typeof process.versions.bun !== "undefined";
3446
+ };
2873
3447
  var createDatabase = (dbPath) => {
3448
+ if (isBun()) {
3449
+ throw new Error(
3450
+ "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
3451
+ );
3452
+ }
2874
3453
  let Database = null;
2875
3454
  try {
2876
3455
  Database = __require("better-sqlite3");
@@ -2895,7 +3474,7 @@ var createSqliteDriver = (options = {}) => {
2895
3474
  );
2896
3475
  const deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2897
3476
  return {
2898
- getItem(key, defaultValue) {
3477
+ async getItem(key, defaultValue) {
2899
3478
  try {
2900
3479
  const row = selectStmt.get(key);
2901
3480
  if (!row || typeof row.value !== "string") return defaultValue;
@@ -2912,14 +3491,14 @@ var createSqliteDriver = (options = {}) => {
2912
3491
  return defaultValue;
2913
3492
  }
2914
3493
  },
2915
- setItem(key, value) {
3494
+ async setItem(key, value) {
2916
3495
  try {
2917
3496
  upsertStmt.run(key, JSON.stringify(value));
2918
3497
  } catch (error) {
2919
3498
  console.error(`SQLite setItem failed for key "${key}":`, error);
2920
3499
  }
2921
3500
  },
2922
- removeItem(key) {
3501
+ async removeItem(key) {
2923
3502
  try {
2924
3503
  deleteStmt.run(key);
2925
3504
  } catch (error) {
@@ -2929,6 +3508,96 @@ var createSqliteDriver = (options = {}) => {
2929
3508
  };
2930
3509
  };
2931
3510
 
3511
+ // storage/drivers/indexedDB.ts
3512
+ var openDatabase = (dbName, storeName) => {
3513
+ return new Promise((resolve, reject) => {
3514
+ const request = indexedDB.open(dbName, 1);
3515
+ request.onupgradeneeded = () => {
3516
+ const db = request.result;
3517
+ if (!db.objectStoreNames.contains(storeName)) {
3518
+ db.createObjectStore(storeName);
3519
+ }
3520
+ };
3521
+ request.onsuccess = () => resolve(request.result);
3522
+ request.onerror = () => reject(request.error);
3523
+ });
3524
+ };
3525
+ var createIndexedDBDriver = (options = {}) => {
3526
+ const dbName = options.dbName || "routstr-sdk";
3527
+ const storeName = options.storeName || "sdk_storage";
3528
+ let dbPromise = null;
3529
+ const getDb = () => {
3530
+ if (!dbPromise) {
3531
+ dbPromise = openDatabase(dbName, storeName);
3532
+ }
3533
+ return dbPromise;
3534
+ };
3535
+ return {
3536
+ async getItem(key, defaultValue) {
3537
+ try {
3538
+ const db = await getDb();
3539
+ return new Promise((resolve, reject) => {
3540
+ const tx = db.transaction(storeName, "readonly");
3541
+ const store = tx.objectStore(storeName);
3542
+ const request = store.get(key);
3543
+ request.onsuccess = () => {
3544
+ const raw = request.result;
3545
+ if (raw === void 0) {
3546
+ resolve(defaultValue);
3547
+ return;
3548
+ }
3549
+ if (typeof raw === "string") {
3550
+ try {
3551
+ resolve(JSON.parse(raw));
3552
+ } catch {
3553
+ if (typeof defaultValue === "string") {
3554
+ resolve(raw);
3555
+ } else {
3556
+ resolve(defaultValue);
3557
+ }
3558
+ }
3559
+ } else {
3560
+ resolve(raw);
3561
+ }
3562
+ };
3563
+ request.onerror = () => reject(request.error);
3564
+ });
3565
+ } catch (error) {
3566
+ console.error(`IndexedDB getItem failed for key "${key}":`, error);
3567
+ return defaultValue;
3568
+ }
3569
+ },
3570
+ async setItem(key, value) {
3571
+ try {
3572
+ const db = await getDb();
3573
+ return new Promise((resolve, reject) => {
3574
+ const tx = db.transaction(storeName, "readwrite");
3575
+ const store = tx.objectStore(storeName);
3576
+ store.put(JSON.stringify(value), key);
3577
+ tx.oncomplete = () => resolve();
3578
+ tx.onerror = () => reject(tx.error);
3579
+ });
3580
+ } catch (error) {
3581
+ console.error(`IndexedDB setItem failed for key "${key}":`, error);
3582
+ }
3583
+ },
3584
+ async removeItem(key) {
3585
+ try {
3586
+ const db = await getDb();
3587
+ return new Promise((resolve, reject) => {
3588
+ const tx = db.transaction(storeName, "readwrite");
3589
+ const store = tx.objectStore(storeName);
3590
+ store.delete(key);
3591
+ tx.oncomplete = () => resolve();
3592
+ tx.onerror = () => reject(tx.error);
3593
+ });
3594
+ } catch (error) {
3595
+ console.error(`IndexedDB removeItem failed for key "${key}":`, error);
3596
+ }
3597
+ }
3598
+ };
3599
+ };
3600
+
2932
3601
  // storage/keys.ts
2933
3602
  var SDK_STORAGE_KEYS = {
2934
3603
  MODELS_FROM_ALL_PROVIDERS: "modelsFromAllProviders",
@@ -2941,7 +3610,9 @@ var SDK_STORAGE_KEYS = {
2941
3610
  LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
2942
3611
  LOCAL_CASHU_TOKENS: "local_cashu_tokens",
2943
3612
  API_KEYS: "api_keys",
2944
- CHILD_KEYS: "child_keys"
3613
+ CHILD_KEYS: "child_keys",
3614
+ ROUTSTR21_MODELS: "routstr21Models",
3615
+ CACHED_RECEIVE_TOKENS: "cached_receive_tokens"
2945
3616
  };
2946
3617
 
2947
3618
  // storage/store.ts
@@ -2959,97 +3630,146 @@ var getTokenBalance = (token) => {
2959
3630
  return 0;
2960
3631
  }
2961
3632
  };
2962
- var createSdkStore = ({ driver }) => {
2963
- return createStore((set, get) => ({
2964
- modelsFromAllProviders: Object.fromEntries(
2965
- Object.entries(
2966
- driver.getItem(
2967
- SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
2968
- {}
2969
- )
2970
- ).map(([baseUrl, models]) => [normalizeBaseUrl(baseUrl), models])
3633
+ var createSdkStore = async ({
3634
+ driver
3635
+ }) => {
3636
+ const [
3637
+ rawModels,
3638
+ lastUsedModel,
3639
+ rawBaseUrls,
3640
+ lastBaseUrlsUpdate,
3641
+ rawDisabledProviders,
3642
+ rawMints,
3643
+ rawInfo,
3644
+ rawLastModelsUpdate,
3645
+ rawCachedTokens,
3646
+ rawApiKeys,
3647
+ rawChildKeys,
3648
+ rawRoutstr21Models,
3649
+ rawCachedReceiveTokens
3650
+ ] = await Promise.all([
3651
+ driver.getItem(
3652
+ SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
3653
+ {}
2971
3654
  ),
2972
- lastUsedModel: driver.getItem(
2973
- SDK_STORAGE_KEYS.LAST_USED_MODEL,
2974
- null
3655
+ driver.getItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, null),
3656
+ driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []),
3657
+ driver.getItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, null),
3658
+ driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []),
3659
+ driver.getItem(
3660
+ SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
3661
+ {}
2975
3662
  ),
2976
- baseUrlsList: driver.getItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, []).map((url) => normalizeBaseUrl(url)),
2977
- lastBaseUrlsUpdate: driver.getItem(
2978
- SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE,
2979
- null
3663
+ driver.getItem(
3664
+ SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
3665
+ {}
2980
3666
  ),
2981
- disabledProviders: driver.getItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, []).map((url) => normalizeBaseUrl(url)),
2982
- mintsFromAllProviders: Object.fromEntries(
2983
- Object.entries(
2984
- driver.getItem(
2985
- SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
2986
- {}
2987
- )
2988
- ).map(([baseUrl, mints]) => [
2989
- normalizeBaseUrl(baseUrl),
2990
- mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
2991
- ])
3667
+ driver.getItem(
3668
+ SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
3669
+ {}
2992
3670
  ),
2993
- infoFromAllProviders: Object.fromEntries(
2994
- Object.entries(
2995
- driver.getItem(
2996
- SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
2997
- {}
2998
- )
2999
- ).map(([baseUrl, info]) => [normalizeBaseUrl(baseUrl), info])
3000
- ),
3001
- lastModelsUpdate: Object.fromEntries(
3002
- Object.entries(
3003
- driver.getItem(
3004
- SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
3005
- {}
3006
- )
3007
- ).map(([baseUrl, timestamp]) => [normalizeBaseUrl(baseUrl), timestamp])
3008
- ),
3009
- cachedTokens: driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []).map((entry) => ({
3010
- ...entry,
3011
- baseUrl: normalizeBaseUrl(entry.baseUrl),
3012
- balance: typeof entry.balance === "number" ? entry.balance : getTokenBalance(entry.token),
3013
- lastUsed: entry.lastUsed ?? null
3014
- })),
3015
- apiKeys: driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []).map((entry) => ({
3016
- ...entry,
3017
- baseUrl: normalizeBaseUrl(entry.baseUrl),
3018
- balance: entry.balance ?? 0,
3019
- lastUsed: entry.lastUsed ?? null
3020
- })),
3021
- childKeys: driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []).map((entry) => ({
3022
- parentBaseUrl: normalizeBaseUrl(entry.parentBaseUrl),
3023
- childKey: entry.childKey,
3024
- balance: entry.balance ?? 0,
3025
- balanceLimit: entry.balanceLimit,
3026
- validityDate: entry.validityDate,
3027
- createdAt: entry.createdAt ?? Date.now()
3028
- })),
3671
+ driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
3672
+ driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
3673
+ driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
3674
+ driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
3675
+ driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, [])
3676
+ ]);
3677
+ const modelsFromAllProviders = Object.fromEntries(
3678
+ Object.entries(rawModels).map(([baseUrl, models]) => [
3679
+ normalizeBaseUrl(baseUrl),
3680
+ models
3681
+ ])
3682
+ );
3683
+ const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl(url));
3684
+ const disabledProviders = rawDisabledProviders.map(
3685
+ (url) => normalizeBaseUrl(url)
3686
+ );
3687
+ const mintsFromAllProviders = Object.fromEntries(
3688
+ Object.entries(rawMints).map(([baseUrl, mints]) => [
3689
+ normalizeBaseUrl(baseUrl),
3690
+ mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
3691
+ ])
3692
+ );
3693
+ const infoFromAllProviders = Object.fromEntries(
3694
+ Object.entries(rawInfo).map(([baseUrl, info]) => [
3695
+ normalizeBaseUrl(baseUrl),
3696
+ info
3697
+ ])
3698
+ );
3699
+ const lastModelsUpdate = Object.fromEntries(
3700
+ Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
3701
+ normalizeBaseUrl(baseUrl),
3702
+ timestamp
3703
+ ])
3704
+ );
3705
+ const cachedTokens = rawCachedTokens.map((entry) => ({
3706
+ ...entry,
3707
+ baseUrl: normalizeBaseUrl(entry.baseUrl),
3708
+ balance: typeof entry.balance === "number" ? entry.balance : getTokenBalance(entry.token),
3709
+ lastUsed: entry.lastUsed ?? null
3710
+ }));
3711
+ const apiKeys = rawApiKeys.map((entry) => ({
3712
+ ...entry,
3713
+ baseUrl: normalizeBaseUrl(entry.baseUrl),
3714
+ balance: entry.balance ?? 0,
3715
+ lastUsed: entry.lastUsed ?? null
3716
+ }));
3717
+ const childKeys = rawChildKeys.map((entry) => ({
3718
+ parentBaseUrl: normalizeBaseUrl(entry.parentBaseUrl),
3719
+ childKey: entry.childKey,
3720
+ balance: entry.balance ?? 0,
3721
+ balanceLimit: entry.balanceLimit,
3722
+ validityDate: entry.validityDate,
3723
+ createdAt: entry.createdAt ?? Date.now()
3724
+ }));
3725
+ const routstr21Models = rawRoutstr21Models;
3726
+ const cachedReceiveTokens = rawCachedReceiveTokens.map((entry) => ({
3727
+ token: entry.token,
3728
+ amount: entry.amount,
3729
+ unit: entry.unit || "sat",
3730
+ createdAt: entry.createdAt ?? Date.now()
3731
+ }));
3732
+ return createStore((set, get) => ({
3733
+ modelsFromAllProviders,
3734
+ lastUsedModel,
3735
+ baseUrlsList,
3736
+ lastBaseUrlsUpdate,
3737
+ disabledProviders,
3738
+ mintsFromAllProviders,
3739
+ infoFromAllProviders,
3740
+ lastModelsUpdate,
3741
+ cachedTokens,
3742
+ apiKeys,
3743
+ childKeys,
3744
+ routstr21Models,
3745
+ cachedReceiveTokens,
3029
3746
  setModelsFromAllProviders: (value) => {
3030
3747
  const normalized = {};
3031
3748
  for (const [baseUrl, models] of Object.entries(value)) {
3032
3749
  normalized[normalizeBaseUrl(baseUrl)] = models;
3033
3750
  }
3034
- driver.setItem(SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS, normalized);
3751
+ void driver.setItem(
3752
+ SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
3753
+ normalized
3754
+ );
3035
3755
  set({ modelsFromAllProviders: normalized });
3036
3756
  },
3037
3757
  setLastUsedModel: (value) => {
3038
- driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
3758
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_USED_MODEL, value);
3039
3759
  set({ lastUsedModel: value });
3040
3760
  },
3041
3761
  setBaseUrlsList: (value) => {
3042
3762
  const normalized = value.map((url) => normalizeBaseUrl(url));
3043
- driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
3763
+ void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
3044
3764
  set({ baseUrlsList: normalized });
3045
3765
  },
3046
3766
  setBaseUrlsLastUpdate: (value) => {
3047
- driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
3767
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_BASE_URLS_UPDATE, value);
3048
3768
  set({ lastBaseUrlsUpdate: value });
3049
3769
  },
3050
3770
  setDisabledProviders: (value) => {
3051
3771
  const normalized = value.map((url) => normalizeBaseUrl(url));
3052
- driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
3772
+ void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
3053
3773
  set({ disabledProviders: normalized });
3054
3774
  },
3055
3775
  setMintsFromAllProviders: (value) => {
@@ -3059,7 +3779,10 @@ var createSdkStore = ({ driver }) => {
3059
3779
  (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
3060
3780
  );
3061
3781
  }
3062
- driver.setItem(SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS, normalized);
3782
+ void driver.setItem(
3783
+ SDK_STORAGE_KEYS.MINTS_FROM_ALL_PROVIDERS,
3784
+ normalized
3785
+ );
3063
3786
  set({ mintsFromAllProviders: normalized });
3064
3787
  },
3065
3788
  setInfoFromAllProviders: (value) => {
@@ -3067,7 +3790,7 @@ var createSdkStore = ({ driver }) => {
3067
3790
  for (const [baseUrl, info] of Object.entries(value)) {
3068
3791
  normalized[normalizeBaseUrl(baseUrl)] = info;
3069
3792
  }
3070
- driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
3793
+ void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
3071
3794
  set({ infoFromAllProviders: normalized });
3072
3795
  },
3073
3796
  setLastModelsUpdate: (value) => {
@@ -3075,7 +3798,7 @@ var createSdkStore = ({ driver }) => {
3075
3798
  for (const [baseUrl, timestamp] of Object.entries(value)) {
3076
3799
  normalized[normalizeBaseUrl(baseUrl)] = timestamp;
3077
3800
  }
3078
- driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
3801
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
3079
3802
  set({ lastModelsUpdate: normalized });
3080
3803
  },
3081
3804
  setCachedTokens: (value) => {
@@ -3087,7 +3810,7 @@ var createSdkStore = ({ driver }) => {
3087
3810
  balance: typeof entry.balance === "number" ? entry.balance : getTokenBalance(entry.token),
3088
3811
  lastUsed: entry.lastUsed ?? null
3089
3812
  }));
3090
- driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
3813
+ void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
3091
3814
  return { cachedTokens: normalized };
3092
3815
  });
3093
3816
  },
@@ -3100,7 +3823,7 @@ var createSdkStore = ({ driver }) => {
3100
3823
  balance: entry.balance ?? 0,
3101
3824
  lastUsed: entry.lastUsed ?? null
3102
3825
  }));
3103
- driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
3826
+ void driver.setItem(SDK_STORAGE_KEYS.API_KEYS, normalized);
3104
3827
  return { apiKeys: normalized };
3105
3828
  });
3106
3829
  },
@@ -3115,9 +3838,23 @@ var createSdkStore = ({ driver }) => {
3115
3838
  validityDate: entry.validityDate,
3116
3839
  createdAt: entry.createdAt ?? Date.now()
3117
3840
  }));
3118
- driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
3841
+ void driver.setItem(SDK_STORAGE_KEYS.CHILD_KEYS, normalized);
3119
3842
  return { childKeys: normalized };
3120
3843
  });
3844
+ },
3845
+ setRoutstr21Models: (value) => {
3846
+ void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
3847
+ set({ routstr21Models: value });
3848
+ },
3849
+ setCachedReceiveTokens: (value) => {
3850
+ const normalized = value.map((entry) => ({
3851
+ token: entry.token,
3852
+ amount: entry.amount,
3853
+ unit: entry.unit || "sat",
3854
+ createdAt: entry.createdAt ?? Date.now()
3855
+ }));
3856
+ void driver.setItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, normalized);
3857
+ set({ cachedReceiveTokens: normalized });
3121
3858
  }
3122
3859
  }));
3123
3860
  };
@@ -3145,7 +3882,9 @@ var createDiscoveryAdapterFromStore = (store) => ({
3145
3882
  getBaseUrlsList: () => store.getState().baseUrlsList,
3146
3883
  setBaseUrlsList: (urls) => store.getState().setBaseUrlsList(urls),
3147
3884
  getBaseUrlsLastUpdate: () => store.getState().lastBaseUrlsUpdate,
3148
- setBaseUrlsLastUpdate: (timestamp) => store.getState().setBaseUrlsLastUpdate(timestamp)
3885
+ setBaseUrlsLastUpdate: (timestamp) => store.getState().setBaseUrlsLastUpdate(timestamp),
3886
+ getRoutstr21Models: () => store.getState().routstr21Models,
3887
+ setRoutstr21Models: (models) => store.getState().setRoutstr21Models(models)
3149
3888
  });
3150
3889
  var createStorageAdapterFromStore = (store) => ({
3151
3890
  getToken: (baseUrl) => {
@@ -3190,10 +3929,21 @@ var createStorageAdapterFromStore = (store) => ({
3190
3929
  );
3191
3930
  store.getState().setCachedTokens(next);
3192
3931
  },
3193
- getPendingTokenDistribution: () => {
3194
- const tokens = store.getState().cachedTokens;
3932
+ getCachedTokenDistribution: () => {
3933
+ const cachedTokens = store.getState().cachedTokens;
3934
+ const distributionMap = {};
3935
+ for (const entry of cachedTokens) {
3936
+ const sum = entry.balance || 0;
3937
+ if (sum > 0) {
3938
+ distributionMap[entry.baseUrl] = (distributionMap[entry.baseUrl] || 0) + sum;
3939
+ }
3940
+ }
3941
+ return Object.entries(distributionMap).map(([baseUrl, amt]) => ({ baseUrl, amount: amt })).sort((a, b) => b.amount - a.amount);
3942
+ },
3943
+ getApiKeyDistribution: () => {
3944
+ const apiKeys = store.getState().apiKeys;
3195
3945
  const distributionMap = {};
3196
- for (const entry of tokens) {
3946
+ for (const entry of apiKeys) {
3197
3947
  const sum = entry.balance || 0;
3198
3948
  if (sum > 0) {
3199
3949
  distributionMap[entry.baseUrl] = (distributionMap[entry.baseUrl] || 0) + sum;
@@ -3220,7 +3970,7 @@ var createStorageAdapterFromStore = (store) => ({
3220
3970
  (key) => key.baseUrl === normalized ? { ...key, lastUsed: Date.now() } : key
3221
3971
  );
3222
3972
  store.getState().setApiKeys(next);
3223
- return entry.key;
3973
+ return entry;
3224
3974
  },
3225
3975
  setApiKey: (baseUrl, key) => {
3226
3976
  const normalized = normalizeBaseUrl(baseUrl);
@@ -3229,20 +3979,16 @@ var createStorageAdapterFromStore = (store) => ({
3229
3979
  (entry) => entry.baseUrl === normalized
3230
3980
  );
3231
3981
  if (existingIndex !== -1) {
3232
- const next = keys.map(
3233
- (entry) => entry.baseUrl === normalized ? { ...entry, key, lastUsed: Date.now() } : entry
3234
- );
3235
- store.getState().setApiKeys(next);
3236
- } else {
3237
- const next = [...keys];
3238
- next.push({
3239
- baseUrl: normalized,
3240
- key,
3241
- balance: 0,
3242
- lastUsed: Date.now()
3243
- });
3244
- store.getState().setApiKeys(next);
3982
+ throw new Error(`ApiKey already exists for baseUrl: ${normalized}`);
3245
3983
  }
3984
+ const next = [...keys];
3985
+ next.push({
3986
+ baseUrl: normalized,
3987
+ key,
3988
+ balance: 0,
3989
+ lastUsed: Date.now()
3990
+ });
3991
+ store.getState().setApiKeys(next);
3246
3992
  },
3247
3993
  updateApiKeyBalance: (baseUrl, balance) => {
3248
3994
  const normalized = normalizeBaseUrl(baseUrl);
@@ -3252,6 +3998,11 @@ var createStorageAdapterFromStore = (store) => ({
3252
3998
  );
3253
3999
  store.getState().setApiKeys(next);
3254
4000
  },
4001
+ removeApiKey: (baseUrl) => {
4002
+ const normalized = normalizeBaseUrl(baseUrl);
4003
+ const next = store.getState().apiKeys.filter((entry) => entry.baseUrl !== normalized);
4004
+ store.getState().setApiKeys(next);
4005
+ },
3255
4006
  getAllApiKeys: () => {
3256
4007
  return store.getState().apiKeys.map((entry) => ({
3257
4008
  baseUrl: entry.baseUrl,
@@ -3327,6 +4078,12 @@ var createStorageAdapterFromStore = (store) => ({
3327
4078
  validityDate: entry.validityDate,
3328
4079
  createdAt: entry.createdAt
3329
4080
  }));
4081
+ },
4082
+ getCachedReceiveTokens: () => {
4083
+ return store.getState().cachedReceiveTokens;
4084
+ },
4085
+ setCachedReceiveTokens: (tokens) => {
4086
+ store.getState().setCachedReceiveTokens(tokens);
3330
4087
  }
3331
4088
  });
3332
4089
  var createProviderRegistryFromStore = (store) => ({
@@ -3377,12 +4134,19 @@ var isNode = () => {
3377
4134
  }
3378
4135
  };
3379
4136
  var defaultDriver = null;
4137
+ var isBun2 = () => {
4138
+ return typeof process.versions.bun !== "undefined";
4139
+ };
3380
4140
  var getDefaultSdkDriver = () => {
3381
4141
  if (defaultDriver) return defaultDriver;
3382
4142
  if (isBrowser()) {
3383
4143
  defaultDriver = localStorageDriver;
3384
4144
  return defaultDriver;
3385
4145
  }
4146
+ if (isBun2()) {
4147
+ defaultDriver = createMemoryDriver();
4148
+ return defaultDriver;
4149
+ }
3386
4150
  if (isNode()) {
3387
4151
  defaultDriver = createSqliteDriver();
3388
4152
  return defaultDriver;
@@ -3390,16 +4154,16 @@ var getDefaultSdkDriver = () => {
3390
4154
  defaultDriver = createMemoryDriver();
3391
4155
  return defaultDriver;
3392
4156
  };
3393
- var defaultStore = null;
4157
+ var defaultStorePromise = null;
3394
4158
  var getDefaultSdkStore = () => {
3395
- if (!defaultStore) {
3396
- defaultStore = createSdkStore({ driver: getDefaultSdkDriver() });
4159
+ if (!defaultStorePromise) {
4160
+ defaultStorePromise = createSdkStore({ driver: getDefaultSdkDriver() });
3397
4161
  }
3398
- return defaultStore;
4162
+ return defaultStorePromise;
3399
4163
  };
3400
- var getDefaultDiscoveryAdapter = () => createDiscoveryAdapterFromStore(getDefaultSdkStore());
3401
- var getDefaultStorageAdapter = () => createStorageAdapterFromStore(getDefaultSdkStore());
3402
- var getDefaultProviderRegistry = () => createProviderRegistryFromStore(getDefaultSdkStore());
4164
+ var getDefaultDiscoveryAdapter = async () => createDiscoveryAdapterFromStore(await getDefaultSdkStore());
4165
+ var getDefaultStorageAdapter = async () => createStorageAdapterFromStore(await getDefaultSdkStore());
4166
+ var getDefaultProviderRegistry = async () => createProviderRegistryFromStore(await getDefaultSdkStore());
3403
4167
 
3404
4168
  // routeRequests.ts
3405
4169
  async function routeRequests(options) {
@@ -3494,6 +4258,7 @@ async function routeRequests(options) {
3494
4258
  if (maxTokens !== void 0) {
3495
4259
  proxiedBody.max_tokens = maxTokens;
3496
4260
  }
4261
+ console.log(modelId);
3497
4262
  response = await client.routeRequest({
3498
4263
  path,
3499
4264
  method: "POST",
@@ -3533,6 +4298,6 @@ function extractStream(requestBody) {
3533
4298
  return stream === true;
3534
4299
  }
3535
4300
 
3536
- export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, createMemoryDriver, createSdkStore, createSqliteDriver, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getProviderEndpoints, isOnionUrl, isTorContext, localStorageDriver, normalizeProviderUrl, routeRequests };
4301
+ export { BalanceManager, CashuSpender, FailoverError, InsufficientBalanceError, MintDiscovery, MintDiscoveryError, MintUnreachableError, ModelManager, ModelNotFoundError, NoProvidersAvailableError, ProviderBootstrapError, ProviderError, ProviderManager, RoutstrClient, SDK_STORAGE_KEYS, StreamProcessor, StreamingError, TokenOperationError, createDiscoveryAdapterFromStore, createIndexedDBDriver, createMemoryDriver, createProviderRegistryFromStore, createSdkStore, createSqliteDriver, createStorageAdapterFromStore, filterBaseUrlsForTor, getDefaultDiscoveryAdapter, getDefaultProviderRegistry, getDefaultSdkDriver, getDefaultSdkStore, getDefaultStorageAdapter, getProviderEndpoints, isOnionUrl, isTorContext, localStorageDriver, normalizeProviderUrl, routeRequests };
3537
4302
  //# sourceMappingURL=index.mjs.map
3538
4303
  //# sourceMappingURL=index.mjs.map