@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/client/index.d.mts +293 -0
- package/dist/client/index.d.ts +293 -0
- package/dist/client/index.js +2663 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +2659 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/discovery/index.d.mts +186 -0
- package/dist/discovery/index.d.ts +186 -0
- package/dist/discovery/index.js +581 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/index.mjs +578 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/index.d.mts +28 -4777
- package/dist/index.d.ts +28 -4777
- package/dist/index.js +1450 -681
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1447 -682
- package/dist/index.mjs.map +1 -1
- package/dist/interfaces-B85Wx7ni.d.mts +171 -0
- package/dist/interfaces-BVNyAmKu.d.ts +171 -0
- package/dist/interfaces-Dnrvxr6N.d.ts +102 -0
- package/dist/interfaces-nanJOqdW.d.mts +102 -0
- package/dist/storage/index.d.mts +134 -0
- package/dist/storage/index.d.ts +134 -0
- package/dist/storage/index.js +861 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +846 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/types-BlHjmWRK.d.mts +222 -0
- package/dist/types-BlHjmWRK.d.ts +222 -0
- package/dist/wallet/index.d.mts +218 -0
- package/dist/wallet/index.d.ts +218 -0
- package/dist/wallet/index.js +1204 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/index.mjs +1201 -0
- package/dist/wallet/index.mjs.map +1 -0
- package/package.json +9 -9
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 ||
|
|
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
|
-
*
|
|
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
|
|
141
|
-
if (
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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,
|
|
726
|
+
constructor(walletAdapter, storageAdapter, _providerRegistry, balanceManager) {
|
|
546
727
|
this.walletAdapter = walletAdapter;
|
|
547
728
|
this.storageAdapter = storageAdapter;
|
|
548
|
-
this.
|
|
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 =
|
|
761
|
+
const balanceInSats = getBalanceInSats(balance, unit);
|
|
561
762
|
normalizedMintBalances[url] = balanceInSats;
|
|
562
763
|
totalMintBalance += balanceInSats;
|
|
563
764
|
}
|
|
564
|
-
const pendingDistribution = this.storageAdapter.
|
|
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
|
-
|
|
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 =
|
|
932
|
+
const balanceInSats = getBalanceInSats(balance, unit);
|
|
661
933
|
totalBalance += balanceInSats;
|
|
662
934
|
}
|
|
663
|
-
const pendingDistribution = this.storageAdapter.
|
|
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
|
-
|
|
669
|
-
|
|
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
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
978
|
+
return {
|
|
979
|
+
token: null,
|
|
980
|
+
status: "failed",
|
|
981
|
+
balance: 0,
|
|
982
|
+
error: tokenResult.error || "Failed to create token"
|
|
983
|
+
};
|
|
699
984
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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:
|
|
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:
|
|
745
|
-
unit:
|
|
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.
|
|
1032
|
+
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
755
1033
|
const balanceForBaseUrl = pendingDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
|
|
756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1082
|
+
* Refund specific providers without retrying spend
|
|
805
1083
|
*/
|
|
806
|
-
async
|
|
807
|
-
const
|
|
808
|
-
const pendingDistribution = this.storageAdapter.
|
|
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
|
-
|
|
1091
|
+
toRefund.map(async (pending) => {
|
|
811
1092
|
const token = this.storageAdapter.getToken(pending.baseUrl);
|
|
812
|
-
|
|
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(
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
*
|
|
1275
|
+
* Refund API key balance - convert remaining API key balance to cashu token
|
|
1050
1276
|
*/
|
|
1051
|
-
async
|
|
1052
|
-
const { mintUrl, baseUrl,
|
|
1053
|
-
if (!
|
|
1054
|
-
return { success: false, message: "
|
|
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
|
|
1061
|
-
let requestId;
|
|
1282
|
+
let fetchResult;
|
|
1062
1283
|
try {
|
|
1063
|
-
|
|
1064
|
-
|
|
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:
|
|
1075
|
-
requestId
|
|
1076
|
-
|
|
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:
|
|
1081
|
-
|
|
1082
|
-
requestId
|
|
1307
|
+
success: receiveResult.success,
|
|
1308
|
+
refundedAmount: totalAmountMsat,
|
|
1309
|
+
requestId: fetchResult.requestId
|
|
1083
1310
|
};
|
|
1084
1311
|
} catch (error) {
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
|
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 ${
|
|
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: `
|
|
1348
|
+
error: `API key refund failed: ${errorData?.detail || response.statusText}`
|
|
1132
1349
|
};
|
|
1133
1350
|
}
|
|
1134
1351
|
const data = await response.json();
|
|
1135
|
-
|
|
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.
|
|
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
|
|
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:
|
|
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
|
|
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))
|
|
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"))
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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(
|
|
2763
|
+
async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken) {
|
|
2190
2764
|
const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
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
|
-
|
|
2806
|
+
tryNextProvider = true;
|
|
2807
|
+
} else
|
|
2808
|
+
throw new ProviderError(
|
|
2203
2809
|
baseUrl,
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
childKeyResult.balanceLimit
|
|
2810
|
+
status,
|
|
2811
|
+
"xcashu refund failed",
|
|
2812
|
+
requestId
|
|
2208
2813
|
);
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
-
|
|
2227
|
-
token
|
|
2228
|
-
|
|
2229
|
-
|
|
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
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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 (
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
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
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
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
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
|
2283
|
-
|
|
2900
|
+
} catch (error) {
|
|
2901
|
+
throw new ProviderError(
|
|
2902
|
+
baseUrl,
|
|
2903
|
+
status,
|
|
2904
|
+
"Failed to refund token",
|
|
2905
|
+
requestId
|
|
2284
2906
|
);
|
|
2285
2907
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
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
|
|
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 (
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
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
|
-
|
|
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.
|
|
3007
|
+
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
2445
3008
|
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
2446
3009
|
} catch (error) {
|
|
2447
|
-
|
|
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.
|
|
2461
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
3151
|
+
this._log("ERROR", "[RoutstrClient] _handleError: Error occurred", error);
|
|
2600
3152
|
if (error instanceof Error) {
|
|
2601
|
-
const
|
|
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 *
|
|
3198
|
+
amount: amount * TOPUP_MARGIN,
|
|
2634
3199
|
baseUrl: "",
|
|
2635
3200
|
reuseToken: false
|
|
2636
3201
|
});
|
|
2637
|
-
if (
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
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
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
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
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
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 =
|
|
3254
|
+
let tokenBalance = 0;
|
|
2694
3255
|
let tokenBalanceUnit = "sat";
|
|
2695
|
-
|
|
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.
|
|
2698
|
-
|
|
2699
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
3292
|
+
baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
|
|
3293
|
+
reuseToken: this.mode === "lazyrefund"
|
|
2718
3294
|
});
|
|
2719
|
-
if (
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
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 = ({
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
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
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
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
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
null
|
|
3665
|
+
driver.getItem(
|
|
3666
|
+
SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS,
|
|
3667
|
+
{}
|
|
2982
3668
|
),
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
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
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
),
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3196
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
4159
|
+
var defaultStorePromise = null;
|
|
3396
4160
|
var getDefaultSdkStore = () => {
|
|
3397
|
-
if (!
|
|
3398
|
-
|
|
4161
|
+
if (!defaultStorePromise) {
|
|
4162
|
+
defaultStorePromise = createSdkStore({ driver: getDefaultSdkDriver() });
|
|
3399
4163
|
}
|
|
3400
|
-
return
|
|
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;
|