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