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