@routstr/sdk 0.2.5 → 0.2.7
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/README.md +1 -2
- package/dist/client/index.d.mts +34 -6
- package/dist/client/index.d.ts +34 -6
- package/dist/client/index.js +773 -479
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +773 -479
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.js +1 -1
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1 -1
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +21 -9
- package/dist/index.d.ts +21 -9
- package/dist/index.js +897 -558
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +895 -559
- package/dist/index.mjs.map +1 -1
- package/dist/{interfaces-CluftN4z.d.ts → interfaces-B62Rw-dd.d.ts} +19 -14
- package/dist/{interfaces-C6Dr6hKy.d.mts → interfaces-C5fLD3jB.d.mts} +19 -14
- package/dist/storage/index.d.mts +39 -162
- package/dist/storage/index.d.ts +39 -162
- package/dist/storage/index.js +529 -193
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +527 -194
- package/dist/storage/index.mjs.map +1 -1
- package/dist/store-DGeLPv9E.d.mts +172 -0
- package/dist/store-h7m23ffq.d.ts +172 -0
- package/dist/wallet/index.d.mts +26 -26
- package/dist/wallet/index.d.ts +26 -26
- package/dist/wallet/index.js +143 -271
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +143 -271
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.mjs
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { createStore } from 'zustand/vanilla';
|
|
2
|
-
import { getDecodedToken } from '@cashu/cashu-ts';
|
|
3
2
|
import { Transform, Readable } from 'stream';
|
|
4
3
|
|
|
5
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
-
}) : x)(function(x) {
|
|
8
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
4
|
// core/errors.ts
|
|
13
5
|
var InsufficientBalanceError = class extends Error {
|
|
14
6
|
constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
|
|
@@ -152,13 +144,8 @@ var CashuSpender = class {
|
|
|
152
144
|
normalizedMintBalances[url] = balanceInSats;
|
|
153
145
|
totalMintBalance += balanceInSats;
|
|
154
146
|
}
|
|
155
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
156
147
|
const providerBalances = {};
|
|
157
148
|
let totalProviderBalance = 0;
|
|
158
|
-
for (const pending of pendingDistribution) {
|
|
159
|
-
providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
|
|
160
|
-
totalProviderBalance += pending.amount;
|
|
161
|
-
}
|
|
162
149
|
const apiKeys = this.storageAdapter.getAllApiKeys();
|
|
163
150
|
for (const apiKey of apiKeys) {
|
|
164
151
|
if (!providerBalances[apiKey.baseUrl]) {
|
|
@@ -379,27 +366,11 @@ var CashuSpender = class {
|
|
|
379
366
|
};
|
|
380
367
|
}
|
|
381
368
|
}
|
|
382
|
-
if (token
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this._log(
|
|
388
|
-
"DEBUG",
|
|
389
|
-
`[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
|
|
390
|
-
);
|
|
391
|
-
const receiveResult = await this.receiveToken(token);
|
|
392
|
-
if (receiveResult.success) {
|
|
393
|
-
this._log(
|
|
394
|
-
"DEBUG",
|
|
395
|
-
`[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
token = this.storageAdapter.getToken(baseUrl);
|
|
399
|
-
} else {
|
|
400
|
-
throw error;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
369
|
+
if (token) {
|
|
370
|
+
this._log(
|
|
371
|
+
"DEBUG",
|
|
372
|
+
`[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
|
|
373
|
+
);
|
|
403
374
|
}
|
|
404
375
|
this._logTransaction("spend", {
|
|
405
376
|
amount: spentAmount,
|
|
@@ -420,19 +391,19 @@ var CashuSpender = class {
|
|
|
420
391
|
};
|
|
421
392
|
}
|
|
422
393
|
/**
|
|
423
|
-
* Try to reuse an existing
|
|
394
|
+
* Try to reuse an existing API key
|
|
424
395
|
*/
|
|
425
396
|
async _tryReuseToken(baseUrl, amount, mintUrl) {
|
|
426
|
-
const
|
|
427
|
-
if (!
|
|
428
|
-
const
|
|
429
|
-
const balanceForBaseUrl =
|
|
430
|
-
this._log("DEBUG", "
|
|
397
|
+
const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
|
|
398
|
+
if (!apiKeyEntry) return null;
|
|
399
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
400
|
+
const balanceForBaseUrl = apiKeyDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
|
|
401
|
+
this._log("DEBUG", "Reusing API key", balanceForBaseUrl, amount);
|
|
431
402
|
if (balanceForBaseUrl > amount) {
|
|
432
403
|
const units = this.walletAdapter.getMintUnits();
|
|
433
404
|
const unit = units[mintUrl] || "sat";
|
|
434
405
|
return {
|
|
435
|
-
token:
|
|
406
|
+
token: apiKeyEntry.key,
|
|
436
407
|
status: "success",
|
|
437
408
|
balance: balanceForBaseUrl,
|
|
438
409
|
unit
|
|
@@ -443,7 +414,8 @@ var CashuSpender = class {
|
|
|
443
414
|
const topUpResult = await this.balanceManager.topUp({
|
|
444
415
|
mintUrl,
|
|
445
416
|
baseUrl,
|
|
446
|
-
amount: topUpAmount
|
|
417
|
+
amount: topUpAmount,
|
|
418
|
+
token: apiKeyEntry.key
|
|
447
419
|
});
|
|
448
420
|
this._log("DEBUG", "TOPUP ", topUpResult);
|
|
449
421
|
if (topUpResult.success && topUpResult.toppedUpAmount) {
|
|
@@ -457,7 +429,7 @@ var CashuSpender = class {
|
|
|
457
429
|
status: "success"
|
|
458
430
|
});
|
|
459
431
|
return {
|
|
460
|
-
token:
|
|
432
|
+
token: apiKeyEntry.key,
|
|
461
433
|
status: "success",
|
|
462
434
|
balance: newBalance,
|
|
463
435
|
unit
|
|
@@ -465,84 +437,131 @@ var CashuSpender = class {
|
|
|
465
437
|
}
|
|
466
438
|
const providerBalance = await this._getProviderTokenBalance(
|
|
467
439
|
baseUrl,
|
|
468
|
-
|
|
440
|
+
apiKeyEntry.key
|
|
469
441
|
);
|
|
470
442
|
this._log("DEBUG", providerBalance);
|
|
471
443
|
if (providerBalance <= 0) {
|
|
472
|
-
this.storageAdapter.
|
|
444
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
473
445
|
}
|
|
474
446
|
}
|
|
475
447
|
return null;
|
|
476
448
|
}
|
|
477
449
|
/**
|
|
478
|
-
* Refund
|
|
450
|
+
* Refund all xcashu tokens from storage by calling the provider's refund endpoint.
|
|
451
|
+
* The xcashu token acts as an API key to claim the refund, and the response contains
|
|
452
|
+
* the actual refunded Cashu token which is then received into the wallet.
|
|
453
|
+
* @param mintUrl - The mint URL for receiving tokens
|
|
454
|
+
* @param excludeBaseUrls - Base URLs to exclude from refund (optional)
|
|
455
|
+
* @returns Results for each xcashu token refund attempt
|
|
479
456
|
*/
|
|
480
|
-
async
|
|
457
|
+
async refundXcashuTokens(mintUrl, excludeBaseUrls) {
|
|
481
458
|
const results = [];
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
apiKey: apiKeyEntryFull.key,
|
|
531
|
-
forceRefund
|
|
532
|
-
});
|
|
533
|
-
if (refundResult.success) {
|
|
534
|
-
this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
|
|
459
|
+
const xcashuTokens = this.storageAdapter.getXcashuTokens();
|
|
460
|
+
const excludedUrls = new Set(excludeBaseUrls || []);
|
|
461
|
+
for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
|
|
462
|
+
if (excludedUrls.has(baseUrl)) continue;
|
|
463
|
+
for (const xcashuToken of tokens) {
|
|
464
|
+
try {
|
|
465
|
+
if (!this.balanceManager) {
|
|
466
|
+
throw new Error("BalanceManager not available for xcashu refund");
|
|
467
|
+
}
|
|
468
|
+
const fetchResult = await this.balanceManager.fetchRefundToken(
|
|
469
|
+
baseUrl,
|
|
470
|
+
xcashuToken.token,
|
|
471
|
+
true
|
|
472
|
+
);
|
|
473
|
+
if (!fetchResult.success || !fetchResult.token) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
fetchResult.error || "Failed to fetch refund token from provider"
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
const receiveResult = await this.receiveToken(fetchResult.token);
|
|
479
|
+
if (receiveResult.success) {
|
|
480
|
+
this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
|
|
481
|
+
results.push({
|
|
482
|
+
baseUrl,
|
|
483
|
+
token: xcashuToken.token,
|
|
484
|
+
success: true
|
|
485
|
+
});
|
|
486
|
+
this._log(
|
|
487
|
+
"DEBUG",
|
|
488
|
+
`[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
|
|
489
|
+
);
|
|
490
|
+
} else {
|
|
491
|
+
const currentTryCount = xcashuToken.tryCount ?? 0;
|
|
492
|
+
const newTryCount = currentTryCount + 1;
|
|
493
|
+
this.storageAdapter.updateXcashuTokenTryCount(
|
|
494
|
+
xcashuToken.token,
|
|
495
|
+
newTryCount
|
|
496
|
+
);
|
|
497
|
+
results.push({
|
|
498
|
+
baseUrl,
|
|
499
|
+
token: xcashuToken.token,
|
|
500
|
+
success: false,
|
|
501
|
+
error: receiveResult.message ?? "Refund failed"
|
|
502
|
+
});
|
|
503
|
+
this._log(
|
|
504
|
+
"DEBUG",
|
|
505
|
+
`[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}`
|
|
506
|
+
);
|
|
535
507
|
}
|
|
508
|
+
} catch (error) {
|
|
509
|
+
const currentTryCount = xcashuToken.tryCount ?? 0;
|
|
510
|
+
const newTryCount = currentTryCount + 1;
|
|
511
|
+
this.storageAdapter.updateXcashuTokenTryCount(
|
|
512
|
+
xcashuToken.token,
|
|
513
|
+
newTryCount
|
|
514
|
+
);
|
|
515
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
536
516
|
results.push({
|
|
537
|
-
baseUrl
|
|
538
|
-
|
|
517
|
+
baseUrl,
|
|
518
|
+
token: xcashuToken.token,
|
|
519
|
+
success: false,
|
|
520
|
+
error: errorMessage
|
|
539
521
|
});
|
|
522
|
+
this._log(
|
|
523
|
+
"ERROR",
|
|
524
|
+
`[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return results;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Refund specific providers without retrying spend
|
|
533
|
+
*/
|
|
534
|
+
async refundProviders(mintUrl, forceRefund) {
|
|
535
|
+
const results = [];
|
|
536
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
537
|
+
for (const apiKeyEntry of apiKeyDistribution) {
|
|
538
|
+
const apiKeyEntryFull = this.storageAdapter.getApiKey(
|
|
539
|
+
apiKeyEntry.baseUrl
|
|
540
|
+
);
|
|
541
|
+
if (apiKeyEntryFull && this.balanceManager) {
|
|
542
|
+
const refundResult = await this.balanceManager.refundApiKey({
|
|
543
|
+
mintUrl,
|
|
544
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
545
|
+
apiKey: apiKeyEntryFull.key,
|
|
546
|
+
forceRefund
|
|
547
|
+
});
|
|
548
|
+
if (refundResult.success) {
|
|
549
|
+
this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
|
|
540
550
|
} else {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
551
|
+
this.storageAdapter.updateApiKeyBalance(
|
|
552
|
+
apiKeyEntry.baseUrl,
|
|
553
|
+
apiKeyEntry.amount
|
|
554
|
+
);
|
|
545
555
|
}
|
|
556
|
+
results.push({
|
|
557
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
558
|
+
success: refundResult.success
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
561
|
+
results.push({
|
|
562
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
563
|
+
success: false
|
|
564
|
+
});
|
|
546
565
|
}
|
|
547
566
|
}
|
|
548
567
|
return results;
|
|
@@ -627,13 +646,8 @@ var BalanceManager = class {
|
|
|
627
646
|
normalizedMintBalances[url] = balanceInSats;
|
|
628
647
|
totalMintBalance += balanceInSats;
|
|
629
648
|
}
|
|
630
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
631
649
|
const providerBalances = {};
|
|
632
650
|
let totalProviderBalance = 0;
|
|
633
|
-
for (const pending of pendingDistribution) {
|
|
634
|
-
providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
|
|
635
|
-
totalProviderBalance += pending.amount;
|
|
636
|
-
}
|
|
637
651
|
const apiKeys = this.storageAdapter.getAllApiKeys();
|
|
638
652
|
for (const apiKey of apiKeys) {
|
|
639
653
|
if (!providerBalances[apiKey.baseUrl]) {
|
|
@@ -648,57 +662,6 @@ var BalanceManager = class {
|
|
|
648
662
|
mintBalances: normalizedMintBalances
|
|
649
663
|
};
|
|
650
664
|
}
|
|
651
|
-
/**
|
|
652
|
-
* Unified refund - handles both NIP-60 and legacy wallet refunds
|
|
653
|
-
*/
|
|
654
|
-
async refund(options) {
|
|
655
|
-
const { mintUrl, baseUrl, token: providedToken } = options;
|
|
656
|
-
const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
|
|
657
|
-
if (!storedToken) {
|
|
658
|
-
console.log("[BalanceManager] No token to refund, returning early");
|
|
659
|
-
return { success: true, message: "No API key to refund" };
|
|
660
|
-
}
|
|
661
|
-
let fetchResult;
|
|
662
|
-
try {
|
|
663
|
-
fetchResult = await this._fetchRefundToken(baseUrl, storedToken);
|
|
664
|
-
if (!fetchResult.success) {
|
|
665
|
-
return {
|
|
666
|
-
success: false,
|
|
667
|
-
message: fetchResult.error || "Refund failed",
|
|
668
|
-
requestId: fetchResult.requestId
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
if (!fetchResult.token) {
|
|
672
|
-
return {
|
|
673
|
-
success: false,
|
|
674
|
-
message: "No token received from refund",
|
|
675
|
-
requestId: fetchResult.requestId
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
if (fetchResult.error === "No balance to refund") {
|
|
679
|
-
console.log(
|
|
680
|
-
"[BalanceManager] No balance to refund, removing stored token"
|
|
681
|
-
);
|
|
682
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
683
|
-
return { success: true, message: "No balance to refund" };
|
|
684
|
-
}
|
|
685
|
-
const receiveResult = await this.cashuSpender.receiveToken(
|
|
686
|
-
fetchResult.token
|
|
687
|
-
);
|
|
688
|
-
const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
|
|
689
|
-
if (!providedToken) {
|
|
690
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
691
|
-
}
|
|
692
|
-
return {
|
|
693
|
-
success: receiveResult.success,
|
|
694
|
-
refundedAmount: totalAmountMsat,
|
|
695
|
-
requestId: fetchResult.requestId
|
|
696
|
-
};
|
|
697
|
-
} catch (error) {
|
|
698
|
-
console.error("[BalanceManager] Refund error", error);
|
|
699
|
-
return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
665
|
/**
|
|
703
666
|
* Refund API key balance - convert remaining API key balance to cashu token
|
|
704
667
|
* @param options - Refund options including forceRefund flag
|
|
@@ -726,7 +689,7 @@ var BalanceManager = class {
|
|
|
726
689
|
}
|
|
727
690
|
let fetchResult;
|
|
728
691
|
try {
|
|
729
|
-
fetchResult = await this.
|
|
692
|
+
fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
|
|
730
693
|
if (!fetchResult.success) {
|
|
731
694
|
return {
|
|
732
695
|
success: false,
|
|
@@ -762,9 +725,9 @@ var BalanceManager = class {
|
|
|
762
725
|
}
|
|
763
726
|
}
|
|
764
727
|
/**
|
|
765
|
-
* Fetch refund token from provider API using API key authentication
|
|
728
|
+
* Fetch refund token from provider API using API key (or xcashu token) authentication
|
|
766
729
|
*/
|
|
767
|
-
async
|
|
730
|
+
async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
|
|
768
731
|
if (!baseUrl) {
|
|
769
732
|
return {
|
|
770
733
|
success: false,
|
|
@@ -778,12 +741,17 @@ var BalanceManager = class {
|
|
|
778
741
|
controller.abort();
|
|
779
742
|
}, 6e4);
|
|
780
743
|
try {
|
|
744
|
+
const headers = {
|
|
745
|
+
"Content-Type": "application/json"
|
|
746
|
+
};
|
|
747
|
+
if (xCashu) {
|
|
748
|
+
headers["X-Cashu"] = apiKeyOrToken;
|
|
749
|
+
} else {
|
|
750
|
+
headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
|
|
751
|
+
}
|
|
781
752
|
const response = await fetch(url, {
|
|
782
753
|
method: "POST",
|
|
783
|
-
headers
|
|
784
|
-
Authorization: `Bearer ${apiKey}`,
|
|
785
|
-
"Content-Type": "application/json"
|
|
786
|
-
},
|
|
754
|
+
headers,
|
|
787
755
|
signal: controller.signal
|
|
788
756
|
});
|
|
789
757
|
clearTimeout(timeoutId);
|
|
@@ -804,10 +772,7 @@ var BalanceManager = class {
|
|
|
804
772
|
};
|
|
805
773
|
} catch (error) {
|
|
806
774
|
clearTimeout(timeoutId);
|
|
807
|
-
console.error(
|
|
808
|
-
"[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
|
|
809
|
-
error
|
|
810
|
-
);
|
|
775
|
+
console.error("[BalanceManager.fetchRefundToken] Fetch error", error);
|
|
811
776
|
if (error instanceof Error) {
|
|
812
777
|
if (error.name === "AbortError") {
|
|
813
778
|
return {
|
|
@@ -834,8 +799,9 @@ var BalanceManager = class {
|
|
|
834
799
|
if (!amount || amount <= 0) {
|
|
835
800
|
return { success: false, message: "Invalid top up amount" };
|
|
836
801
|
}
|
|
837
|
-
const
|
|
838
|
-
|
|
802
|
+
const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
|
|
803
|
+
const apiKey = providedToken || apiKeyEntry?.key;
|
|
804
|
+
if (!apiKey) {
|
|
839
805
|
return { success: false, message: "No API key available for top up" };
|
|
840
806
|
}
|
|
841
807
|
let cashuToken = null;
|
|
@@ -853,11 +819,7 @@ var BalanceManager = class {
|
|
|
853
819
|
};
|
|
854
820
|
}
|
|
855
821
|
cashuToken = tokenResult.token;
|
|
856
|
-
const topUpResult = await this._postTopUp(
|
|
857
|
-
baseUrl,
|
|
858
|
-
storedToken,
|
|
859
|
-
cashuToken
|
|
860
|
-
);
|
|
822
|
+
const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
|
|
861
823
|
requestId = topUpResult.requestId;
|
|
862
824
|
console.log(topUpResult);
|
|
863
825
|
if (!topUpResult.success) {
|
|
@@ -1070,38 +1032,11 @@ var BalanceManager = class {
|
|
|
1070
1032
|
return candidates;
|
|
1071
1033
|
}
|
|
1072
1034
|
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
|
|
1073
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
1074
1035
|
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
1075
1036
|
const forceRefund = retryCount >= 2;
|
|
1076
|
-
const toRefund = pendingDistribution.filter(
|
|
1077
|
-
(pending) => pending.baseUrl !== baseUrl
|
|
1078
|
-
);
|
|
1079
1037
|
const apiKeysToRefund = apiKeyDistribution.filter(
|
|
1080
1038
|
(apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
|
|
1081
1039
|
);
|
|
1082
|
-
const tokenRefundResults = await Promise.allSettled(
|
|
1083
|
-
toRefund.map(async (pending) => {
|
|
1084
|
-
const token = this.storageAdapter.getToken(pending.baseUrl);
|
|
1085
|
-
if (!token) {
|
|
1086
|
-
return { baseUrl: pending.baseUrl, success: false };
|
|
1087
|
-
}
|
|
1088
|
-
const tokenBalance = await this.getTokenBalance(token, pending.baseUrl);
|
|
1089
|
-
if (tokenBalance.reserved > 0) {
|
|
1090
|
-
return { baseUrl: pending.baseUrl, success: false };
|
|
1091
|
-
}
|
|
1092
|
-
const result = await this.refund({
|
|
1093
|
-
mintUrl,
|
|
1094
|
-
baseUrl: pending.baseUrl,
|
|
1095
|
-
token
|
|
1096
|
-
});
|
|
1097
|
-
return { baseUrl: pending.baseUrl, success: result.success };
|
|
1098
|
-
})
|
|
1099
|
-
);
|
|
1100
|
-
for (const result of tokenRefundResults) {
|
|
1101
|
-
if (result.status === "fulfilled" && result.value.success) {
|
|
1102
|
-
this.storageAdapter.removeToken(result.value.baseUrl);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
1040
|
const apiKeyRefundResults = await Promise.allSettled(
|
|
1106
1041
|
apiKeysToRefund.map(async (apiKeyEntry) => {
|
|
1107
1042
|
const fullApiKeyEntry = this.storageAdapter.getApiKey(
|
|
@@ -1125,77 +1060,6 @@ var BalanceManager = class {
|
|
|
1125
1060
|
}
|
|
1126
1061
|
}
|
|
1127
1062
|
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Fetch refund token from provider API
|
|
1130
|
-
*/
|
|
1131
|
-
async _fetchRefundToken(baseUrl, storedToken) {
|
|
1132
|
-
if (!baseUrl) {
|
|
1133
|
-
return {
|
|
1134
|
-
success: false,
|
|
1135
|
-
error: "No base URL configured"
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
1139
|
-
const url = `${normalizedBaseUrl}v1/wallet/refund`;
|
|
1140
|
-
const controller = new AbortController();
|
|
1141
|
-
const timeoutId = setTimeout(() => {
|
|
1142
|
-
controller.abort();
|
|
1143
|
-
}, 6e4);
|
|
1144
|
-
try {
|
|
1145
|
-
const response = await fetch(url, {
|
|
1146
|
-
method: "POST",
|
|
1147
|
-
headers: {
|
|
1148
|
-
Authorization: `Bearer ${storedToken}`,
|
|
1149
|
-
"Content-Type": "application/json"
|
|
1150
|
-
},
|
|
1151
|
-
signal: controller.signal
|
|
1152
|
-
});
|
|
1153
|
-
clearTimeout(timeoutId);
|
|
1154
|
-
const requestId = response.headers.get("x-routstr-request-id") || void 0;
|
|
1155
|
-
if (!response.ok) {
|
|
1156
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1157
|
-
if (response.status === 400 && errorData?.detail === "No balance to refund") {
|
|
1158
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
1159
|
-
return {
|
|
1160
|
-
success: false,
|
|
1161
|
-
requestId,
|
|
1162
|
-
error: "No balance to refund"
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
return {
|
|
1166
|
-
success: false,
|
|
1167
|
-
requestId,
|
|
1168
|
-
error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
const data = await response.json();
|
|
1172
|
-
console.log("refund rsule", data);
|
|
1173
|
-
return {
|
|
1174
|
-
success: true,
|
|
1175
|
-
token: data.token,
|
|
1176
|
-
requestId
|
|
1177
|
-
};
|
|
1178
|
-
} catch (error) {
|
|
1179
|
-
clearTimeout(timeoutId);
|
|
1180
|
-
console.error("[BalanceManager._fetchRefundToken] Fetch error", error);
|
|
1181
|
-
if (error instanceof Error) {
|
|
1182
|
-
if (error.name === "AbortError") {
|
|
1183
|
-
return {
|
|
1184
|
-
success: false,
|
|
1185
|
-
error: "Request timed out after 1 minute"
|
|
1186
|
-
};
|
|
1187
|
-
}
|
|
1188
|
-
return {
|
|
1189
|
-
success: false,
|
|
1190
|
-
error: error.message
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
return {
|
|
1194
|
-
success: false,
|
|
1195
|
-
error: "Unknown error occurred during refund request"
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
1063
|
/**
|
|
1200
1064
|
* Post topup request to provider API
|
|
1201
1065
|
*/
|
|
@@ -1321,7 +1185,7 @@ var BalanceManager = class {
|
|
|
1321
1185
|
console.log(response.status);
|
|
1322
1186
|
const data = await response.json();
|
|
1323
1187
|
console.log("FAILED ", data);
|
|
1324
|
-
const isInvalidApiKey = response.status === 401 && data?.code === "invalid_api_key" && data?.message?.includes("proofs already spent");
|
|
1188
|
+
const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
|
|
1325
1189
|
return {
|
|
1326
1190
|
amount: -1,
|
|
1327
1191
|
reserved: data.reserved ?? 0,
|
|
@@ -1763,8 +1627,13 @@ function isInsecureHttpUrl(url) {
|
|
|
1763
1627
|
return url.startsWith("http://");
|
|
1764
1628
|
}
|
|
1765
1629
|
var ProviderManager = class _ProviderManager {
|
|
1766
|
-
constructor(providerRegistry) {
|
|
1630
|
+
constructor(providerRegistry, store) {
|
|
1767
1631
|
this.providerRegistry = providerRegistry;
|
|
1632
|
+
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1633
|
+
if (store) {
|
|
1634
|
+
this.store = store;
|
|
1635
|
+
this.hydrateFromStore();
|
|
1636
|
+
}
|
|
1768
1637
|
}
|
|
1769
1638
|
failedProviders = /* @__PURE__ */ new Set();
|
|
1770
1639
|
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
@@ -1773,14 +1642,57 @@ var ProviderManager = class _ProviderManager {
|
|
|
1773
1642
|
providersOnCoolDown = [];
|
|
1774
1643
|
/** Cooldown duration in milliseconds (5 minutes) */
|
|
1775
1644
|
static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
|
|
1645
|
+
/** Optional persistent store for failure tracking */
|
|
1646
|
+
store = null;
|
|
1647
|
+
/** Instance ID for debugging */
|
|
1648
|
+
instanceId;
|
|
1649
|
+
/**
|
|
1650
|
+
* Hydrate in-memory state from persistent store
|
|
1651
|
+
*/
|
|
1652
|
+
hydrateFromStore() {
|
|
1653
|
+
if (!this.store) return;
|
|
1654
|
+
const state = this.store.getState();
|
|
1655
|
+
this.failedProviders = new Set(state.failedProviders);
|
|
1656
|
+
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
1657
|
+
const now = Date.now();
|
|
1658
|
+
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
1659
|
+
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
1660
|
+
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
1661
|
+
console.log(`[ProviderManager:${this.instanceId}] Hydrated from store:`);
|
|
1662
|
+
console.log(` failedProviders: ${this.failedProviders.size}`);
|
|
1663
|
+
console.log(` lastFailed: ${this.lastFailed.size}`);
|
|
1664
|
+
console.log(` providersOnCooldown: ${this.providersOnCoolDown.length}`);
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Get instance ID for debugging
|
|
1668
|
+
*/
|
|
1669
|
+
getInstanceId() {
|
|
1670
|
+
return this.instanceId;
|
|
1671
|
+
}
|
|
1776
1672
|
/**
|
|
1777
1673
|
* Clean up expired cooldown entries
|
|
1778
1674
|
*/
|
|
1779
1675
|
cleanupExpiredCooldowns() {
|
|
1780
1676
|
const now = Date.now();
|
|
1677
|
+
const before = this.providersOnCoolDown.length;
|
|
1781
1678
|
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1782
|
-
([, timestamp]) =>
|
|
1679
|
+
([url, timestamp]) => {
|
|
1680
|
+
const age = now - timestamp;
|
|
1681
|
+
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1682
|
+
if (isExpired) {
|
|
1683
|
+
console.log(
|
|
1684
|
+
`[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
return !isExpired;
|
|
1688
|
+
}
|
|
1783
1689
|
);
|
|
1690
|
+
const after = this.providersOnCoolDown.length;
|
|
1691
|
+
if (before !== after) {
|
|
1692
|
+
console.log(
|
|
1693
|
+
`[cleanupExpiredCooldowns:${this.instanceId}] Cleaned up ${before - after} expired cooldown(s), ${after} remaining`
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1784
1696
|
}
|
|
1785
1697
|
/**
|
|
1786
1698
|
* Get the cooldown duration in milliseconds
|
|
@@ -1793,7 +1705,8 @@ var ProviderManager = class _ProviderManager {
|
|
|
1793
1705
|
*/
|
|
1794
1706
|
isOnCooldown(baseUrl) {
|
|
1795
1707
|
this.cleanupExpiredCooldowns();
|
|
1796
|
-
|
|
1708
|
+
const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
|
|
1709
|
+
return result;
|
|
1797
1710
|
}
|
|
1798
1711
|
/**
|
|
1799
1712
|
* Get all providers currently on cooldown
|
|
@@ -1807,6 +1720,9 @@ var ProviderManager = class _ProviderManager {
|
|
|
1807
1720
|
*/
|
|
1808
1721
|
resetFailedProviders() {
|
|
1809
1722
|
this.failedProviders.clear();
|
|
1723
|
+
if (this.store) {
|
|
1724
|
+
this.store.getState().setFailedProviders([]);
|
|
1725
|
+
}
|
|
1810
1726
|
}
|
|
1811
1727
|
/**
|
|
1812
1728
|
* Get the last failed timestamp for a provider
|
|
@@ -1827,13 +1743,62 @@ var ProviderManager = class _ProviderManager {
|
|
|
1827
1743
|
markFailed(baseUrl) {
|
|
1828
1744
|
const now = Date.now();
|
|
1829
1745
|
const lastFailure = this.lastFailed.get(baseUrl);
|
|
1746
|
+
console.log(`[markFailed:${this.instanceId}] baseUrl: ${baseUrl}`);
|
|
1747
|
+
console.log(
|
|
1748
|
+
`[markFailed:${this.instanceId}] lastFailure from map: ${lastFailure}`
|
|
1749
|
+
);
|
|
1750
|
+
console.log(
|
|
1751
|
+
`[markFailed:${this.instanceId}] current timestamp (now): ${now}`
|
|
1752
|
+
);
|
|
1753
|
+
console.log(
|
|
1754
|
+
`[markFailed:${this.instanceId}] COOLDOWN_DURATION_MS: ${_ProviderManager.COOLDOWN_DURATION_MS}`
|
|
1755
|
+
);
|
|
1756
|
+
if (lastFailure !== void 0) {
|
|
1757
|
+
const timeSinceLastFailure = now - lastFailure;
|
|
1758
|
+
console.log(
|
|
1759
|
+
`[markFailed:${this.instanceId}] timeSinceLastFailure: ${timeSinceLastFailure}ms`
|
|
1760
|
+
);
|
|
1761
|
+
console.log(
|
|
1762
|
+
`[markFailed:${this.instanceId}] isWithinCooldownWindow: ${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1830
1765
|
this.lastFailed.set(baseUrl, now);
|
|
1831
1766
|
this.failedProviders.add(baseUrl);
|
|
1767
|
+
if (this.store) {
|
|
1768
|
+
this.store.getState().setLastFailedTimestamp(baseUrl, now);
|
|
1769
|
+
this.store.getState().addFailedProvider(baseUrl);
|
|
1770
|
+
}
|
|
1771
|
+
console.log(
|
|
1772
|
+
`[markFailed:${this.instanceId}] Updated lastFailed map for ${baseUrl} to ${now}`
|
|
1773
|
+
);
|
|
1774
|
+
console.log(
|
|
1775
|
+
`[markFailed:${this.instanceId}] failedProviders set size: ${this.failedProviders.size}`
|
|
1776
|
+
);
|
|
1832
1777
|
if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
|
|
1778
|
+
console.log(
|
|
1779
|
+
`[markFailed:${this.instanceId}] Second failure detected within cooldown window for ${baseUrl}`
|
|
1780
|
+
);
|
|
1833
1781
|
if (!this.isOnCooldown(baseUrl)) {
|
|
1834
1782
|
this.providersOnCoolDown.push([baseUrl, now]);
|
|
1783
|
+
if (this.store) {
|
|
1784
|
+
this.store.getState().addProviderOnCooldown(baseUrl, now);
|
|
1785
|
+
}
|
|
1786
|
+
console.log(
|
|
1787
|
+
`[markFailed:${this.instanceId}] Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
|
|
1788
|
+
);
|
|
1789
|
+
} else {
|
|
1790
|
+
console.log(
|
|
1791
|
+
`[markFailed:${this.instanceId}] Provider ${baseUrl} is already on cooldown`
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
} else {
|
|
1795
|
+
if (lastFailure === void 0) {
|
|
1835
1796
|
console.log(
|
|
1836
|
-
`
|
|
1797
|
+
`[markFailed:${this.instanceId}] First failure for ${baseUrl} - not adding to cooldown yet`
|
|
1798
|
+
);
|
|
1799
|
+
} else {
|
|
1800
|
+
console.log(
|
|
1801
|
+
`[markFailed:${this.instanceId}] Failure outside cooldown window for ${baseUrl} (timeSinceLastFailure: ${now - lastFailure}ms)`
|
|
1837
1802
|
);
|
|
1838
1803
|
}
|
|
1839
1804
|
}
|
|
@@ -1845,18 +1810,27 @@ var ProviderManager = class _ProviderManager {
|
|
|
1845
1810
|
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1846
1811
|
([url]) => url !== baseUrl
|
|
1847
1812
|
);
|
|
1813
|
+
if (this.store) {
|
|
1814
|
+
this.store.getState().removeProviderFromCooldown(baseUrl);
|
|
1815
|
+
}
|
|
1848
1816
|
}
|
|
1849
1817
|
/**
|
|
1850
1818
|
* Clear all cooldown tracking
|
|
1851
1819
|
*/
|
|
1852
1820
|
clearCooldowns() {
|
|
1853
1821
|
this.providersOnCoolDown = [];
|
|
1822
|
+
if (this.store) {
|
|
1823
|
+
this.store.getState().clearProvidersOnCooldown();
|
|
1824
|
+
}
|
|
1854
1825
|
}
|
|
1855
1826
|
/**
|
|
1856
1827
|
* Clear all failure tracking (lastFailed timestamps)
|
|
1857
1828
|
*/
|
|
1858
1829
|
clearFailureHistory() {
|
|
1859
1830
|
this.lastFailed.clear();
|
|
1831
|
+
if (this.store) {
|
|
1832
|
+
this.store.getState().setLastFailed({});
|
|
1833
|
+
}
|
|
1860
1834
|
}
|
|
1861
1835
|
/**
|
|
1862
1836
|
* Check if a provider has failed
|
|
@@ -2179,38 +2153,54 @@ var createMemoryDriver = (seed) => {
|
|
|
2179
2153
|
var isBun = () => {
|
|
2180
2154
|
return typeof process.versions.bun !== "undefined";
|
|
2181
2155
|
};
|
|
2182
|
-
var
|
|
2156
|
+
var cachedDbModule = null;
|
|
2157
|
+
var loadDatabase = async (dbPath) => {
|
|
2183
2158
|
if (isBun()) {
|
|
2184
2159
|
throw new Error(
|
|
2185
|
-
"SQLite driver not supported in Bun. Use
|
|
2160
|
+
"SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
|
|
2186
2161
|
);
|
|
2187
2162
|
}
|
|
2188
|
-
let Database = null;
|
|
2189
2163
|
try {
|
|
2190
|
-
|
|
2164
|
+
if (!cachedDbModule) {
|
|
2165
|
+
cachedDbModule = (await import('better-sqlite3')).default;
|
|
2166
|
+
}
|
|
2167
|
+
return new cachedDbModule(dbPath);
|
|
2191
2168
|
} catch (error) {
|
|
2192
2169
|
throw new Error(
|
|
2193
2170
|
`better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
|
|
2194
2171
|
);
|
|
2195
2172
|
}
|
|
2196
|
-
return new Database(dbPath);
|
|
2197
2173
|
};
|
|
2198
2174
|
var createSqliteDriver = (options = {}) => {
|
|
2199
2175
|
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2200
2176
|
const tableName = options.tableName || "sdk_storage";
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
const
|
|
2206
|
-
|
|
2207
|
-
|
|
2177
|
+
let db;
|
|
2178
|
+
let selectStmt;
|
|
2179
|
+
let upsertStmt;
|
|
2180
|
+
let deleteStmt;
|
|
2181
|
+
const initDb = async () => {
|
|
2182
|
+
if (!db) {
|
|
2183
|
+
db = await loadDatabase(dbPath);
|
|
2184
|
+
db.exec(
|
|
2185
|
+
`CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
|
|
2186
|
+
);
|
|
2187
|
+
selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
|
|
2188
|
+
upsertStmt = db.prepare(
|
|
2189
|
+
`INSERT INTO ${tableName} (key, value) VALUES (?, ?)
|
|
2208
2190
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value`
|
|
2209
|
-
|
|
2210
|
-
|
|
2191
|
+
);
|
|
2192
|
+
deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
const ensureInit = async () => {
|
|
2196
|
+
if (!db) {
|
|
2197
|
+
await initDb();
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2211
2200
|
return {
|
|
2212
2201
|
async getItem(key, defaultValue) {
|
|
2213
2202
|
try {
|
|
2203
|
+
await ensureInit();
|
|
2214
2204
|
const row = selectStmt.get(key);
|
|
2215
2205
|
if (!row || typeof row.value !== "string") return defaultValue;
|
|
2216
2206
|
try {
|
|
@@ -2228,6 +2218,7 @@ var createSqliteDriver = (options = {}) => {
|
|
|
2228
2218
|
},
|
|
2229
2219
|
async setItem(key, value) {
|
|
2230
2220
|
try {
|
|
2221
|
+
await ensureInit();
|
|
2231
2222
|
upsertStmt.run(key, JSON.stringify(value));
|
|
2232
2223
|
} catch (error) {
|
|
2233
2224
|
console.error(`SQLite setItem failed for key "${key}":`, error);
|
|
@@ -2235,6 +2226,7 @@ var createSqliteDriver = (options = {}) => {
|
|
|
2235
2226
|
},
|
|
2236
2227
|
async removeItem(key) {
|
|
2237
2228
|
try {
|
|
2229
|
+
await ensureInit();
|
|
2238
2230
|
deleteStmt.run(key);
|
|
2239
2231
|
} catch (error) {
|
|
2240
2232
|
console.error(`SQLite removeItem failed for key "${key}":`, error);
|
|
@@ -2253,14 +2245,17 @@ var SDK_STORAGE_KEYS = {
|
|
|
2253
2245
|
INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
|
|
2254
2246
|
LAST_MODELS_UPDATE: "lastModelsUpdate",
|
|
2255
2247
|
LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
|
|
2256
|
-
LOCAL_CASHU_TOKENS: "local_cashu_tokens",
|
|
2257
2248
|
API_KEYS: "api_keys",
|
|
2258
2249
|
CHILD_KEYS: "child_keys",
|
|
2250
|
+
XCASHU_TOKENS: "xcashu_tokens",
|
|
2259
2251
|
ROUTSTR21_MODELS: "routstr21Models",
|
|
2260
2252
|
LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
|
|
2261
2253
|
CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
|
|
2262
2254
|
USAGE_TRACKING: "usage_tracking",
|
|
2263
|
-
CLIENT_IDS: "client_ids"
|
|
2255
|
+
CLIENT_IDS: "client_ids",
|
|
2256
|
+
FAILED_PROVIDERS: "failed_providers",
|
|
2257
|
+
LAST_FAILED: "last_failed",
|
|
2258
|
+
PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
|
|
2264
2259
|
};
|
|
2265
2260
|
|
|
2266
2261
|
// storage/usageTracking/indexedDB.ts
|
|
@@ -2446,21 +2441,23 @@ var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUr
|
|
|
2446
2441
|
var isBun2 = () => {
|
|
2447
2442
|
return typeof process.versions.bun !== "undefined";
|
|
2448
2443
|
};
|
|
2449
|
-
var
|
|
2444
|
+
var cachedDbModule2 = null;
|
|
2445
|
+
var loadDatabase2 = async (dbPath) => {
|
|
2450
2446
|
if (isBun2()) {
|
|
2451
2447
|
throw new Error(
|
|
2452
2448
|
"SQLite driver not supported in Bun. Use createMemoryDriver() instead."
|
|
2453
2449
|
);
|
|
2454
2450
|
}
|
|
2455
|
-
let Database = null;
|
|
2456
2451
|
try {
|
|
2457
|
-
|
|
2452
|
+
if (!cachedDbModule2) {
|
|
2453
|
+
cachedDbModule2 = (await import('better-sqlite3')).default;
|
|
2454
|
+
}
|
|
2455
|
+
return new cachedDbModule2(dbPath);
|
|
2458
2456
|
} catch (error) {
|
|
2459
2457
|
throw new Error(
|
|
2460
2458
|
`better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
|
|
2461
2459
|
);
|
|
2462
2460
|
}
|
|
2463
|
-
return new Database(dbPath);
|
|
2464
2461
|
};
|
|
2465
2462
|
var buildWhereClause = (options = {}) => {
|
|
2466
2463
|
const clauses = [];
|
|
@@ -2497,38 +2494,49 @@ var buildWhereClause = (options = {}) => {
|
|
|
2497
2494
|
var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
2498
2495
|
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2499
2496
|
const tableName = options.tableName || "usage_tracking";
|
|
2500
|
-
const db = createDatabase2(dbPath);
|
|
2501
2497
|
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2502
|
-
db
|
|
2503
|
-
|
|
2504
|
-
id TEXT PRIMARY KEY,
|
|
2505
|
-
timestamp INTEGER NOT NULL,
|
|
2506
|
-
model_id TEXT NOT NULL,
|
|
2507
|
-
base_url TEXT NOT NULL,
|
|
2508
|
-
request_id TEXT NOT NULL,
|
|
2509
|
-
cost REAL NOT NULL,
|
|
2510
|
-
sats_cost REAL NOT NULL,
|
|
2511
|
-
prompt_tokens INTEGER NOT NULL,
|
|
2512
|
-
completion_tokens INTEGER NOT NULL,
|
|
2513
|
-
total_tokens INTEGER NOT NULL,
|
|
2514
|
-
client TEXT,
|
|
2515
|
-
session_id TEXT,
|
|
2516
|
-
tags TEXT
|
|
2517
|
-
);
|
|
2518
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
2519
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
2520
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
2521
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
2522
|
-
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
2523
|
-
`);
|
|
2524
|
-
const insertStmt = db.prepare(`
|
|
2525
|
-
INSERT OR REPLACE INTO ${tableName} (
|
|
2526
|
-
id, timestamp, model_id, base_url, request_id,
|
|
2527
|
-
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2528
|
-
client, session_id, tags
|
|
2529
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2530
|
-
`);
|
|
2498
|
+
let db;
|
|
2499
|
+
let insertStmt;
|
|
2531
2500
|
let migrationComplete = false;
|
|
2501
|
+
const initDb = async () => {
|
|
2502
|
+
if (!db) {
|
|
2503
|
+
db = await loadDatabase2(dbPath);
|
|
2504
|
+
db.exec(`
|
|
2505
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2506
|
+
id TEXT PRIMARY KEY,
|
|
2507
|
+
timestamp INTEGER NOT NULL,
|
|
2508
|
+
model_id TEXT NOT NULL,
|
|
2509
|
+
base_url TEXT NOT NULL,
|
|
2510
|
+
request_id TEXT NOT NULL,
|
|
2511
|
+
cost REAL NOT NULL,
|
|
2512
|
+
sats_cost REAL NOT NULL,
|
|
2513
|
+
prompt_tokens INTEGER NOT NULL,
|
|
2514
|
+
completion_tokens INTEGER NOT NULL,
|
|
2515
|
+
total_tokens INTEGER NOT NULL,
|
|
2516
|
+
client TEXT,
|
|
2517
|
+
session_id TEXT,
|
|
2518
|
+
tags TEXT
|
|
2519
|
+
);
|
|
2520
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
|
|
2521
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
|
|
2522
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
|
|
2523
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
|
|
2524
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
|
|
2525
|
+
`);
|
|
2526
|
+
insertStmt = db.prepare(`
|
|
2527
|
+
INSERT OR REPLACE INTO ${tableName} (
|
|
2528
|
+
id, timestamp, model_id, base_url, request_id,
|
|
2529
|
+
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2530
|
+
client, session_id, tags
|
|
2531
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2532
|
+
`);
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
const ensureInit = async () => {
|
|
2536
|
+
if (!db) {
|
|
2537
|
+
await initDb();
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2532
2540
|
const appendOne = (entry) => {
|
|
2533
2541
|
insertStmt.run(
|
|
2534
2542
|
entry.id,
|
|
@@ -2586,19 +2594,23 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
2586
2594
|
});
|
|
2587
2595
|
return {
|
|
2588
2596
|
async migrate() {
|
|
2597
|
+
await ensureInit();
|
|
2589
2598
|
await ensureMigrated();
|
|
2590
2599
|
},
|
|
2591
2600
|
async append(entry) {
|
|
2601
|
+
await ensureInit();
|
|
2592
2602
|
await ensureMigrated();
|
|
2593
2603
|
appendOne(entry);
|
|
2594
2604
|
},
|
|
2595
2605
|
async appendMany(entries) {
|
|
2606
|
+
await ensureInit();
|
|
2596
2607
|
await ensureMigrated();
|
|
2597
2608
|
for (const entry of entries) {
|
|
2598
2609
|
appendOne(entry);
|
|
2599
2610
|
}
|
|
2600
2611
|
},
|
|
2601
2612
|
async list(options2 = {}) {
|
|
2613
|
+
await ensureInit();
|
|
2602
2614
|
await ensureMigrated();
|
|
2603
2615
|
const { sql, params } = buildWhereClause(options2);
|
|
2604
2616
|
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
@@ -2611,6 +2623,7 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
2611
2623
|
return rows.map(mapRow);
|
|
2612
2624
|
},
|
|
2613
2625
|
async count(options2 = {}) {
|
|
2626
|
+
await ensureInit();
|
|
2614
2627
|
await ensureMigrated();
|
|
2615
2628
|
const { sql, params } = buildWhereClause(options2);
|
|
2616
2629
|
const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
|
|
@@ -2618,20 +2631,197 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
2618
2631
|
return Number(row?.count ?? 0);
|
|
2619
2632
|
},
|
|
2620
2633
|
async deleteOlderThan(timestamp) {
|
|
2634
|
+
await ensureInit();
|
|
2621
2635
|
await ensureMigrated();
|
|
2622
2636
|
const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
|
|
2623
2637
|
const result = stmt.run(timestamp);
|
|
2624
2638
|
return result.changes;
|
|
2625
2639
|
},
|
|
2626
2640
|
async clear() {
|
|
2641
|
+
await ensureInit();
|
|
2627
2642
|
await ensureMigrated();
|
|
2628
2643
|
db.prepare(`DELETE FROM ${tableName}`).run();
|
|
2629
2644
|
}
|
|
2630
2645
|
};
|
|
2631
2646
|
};
|
|
2632
2647
|
|
|
2633
|
-
// storage/usageTracking/
|
|
2648
|
+
// storage/usageTracking/bunSqlite.ts
|
|
2649
|
+
var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
|
|
2634
2650
|
var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2651
|
+
var buildWhereClause2 = (options = {}) => {
|
|
2652
|
+
const clauses = [];
|
|
2653
|
+
const params = [];
|
|
2654
|
+
if (typeof options.before === "number") {
|
|
2655
|
+
clauses.push("timestamp < ?");
|
|
2656
|
+
params.push(options.before);
|
|
2657
|
+
}
|
|
2658
|
+
if (typeof options.after === "number") {
|
|
2659
|
+
clauses.push("timestamp > ?");
|
|
2660
|
+
params.push(options.after);
|
|
2661
|
+
}
|
|
2662
|
+
if (options.modelId) {
|
|
2663
|
+
clauses.push("model_id = ?");
|
|
2664
|
+
params.push(options.modelId);
|
|
2665
|
+
}
|
|
2666
|
+
if (options.baseUrl) {
|
|
2667
|
+
clauses.push("base_url = ?");
|
|
2668
|
+
params.push(normalizeBaseUrl3(options.baseUrl));
|
|
2669
|
+
}
|
|
2670
|
+
if (options.sessionId) {
|
|
2671
|
+
clauses.push("session_id = ?");
|
|
2672
|
+
params.push(options.sessionId);
|
|
2673
|
+
}
|
|
2674
|
+
if (options.client) {
|
|
2675
|
+
clauses.push("client = ?");
|
|
2676
|
+
params.push(options.client);
|
|
2677
|
+
}
|
|
2678
|
+
return {
|
|
2679
|
+
sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
|
|
2680
|
+
params
|
|
2681
|
+
};
|
|
2682
|
+
};
|
|
2683
|
+
var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
2684
|
+
const dbPath = options.dbPath || "routstr.sqlite";
|
|
2685
|
+
const tableName = options.tableName || "usage_tracking";
|
|
2686
|
+
const legacyStorageDriver = options.legacyStorageDriver;
|
|
2687
|
+
const SQLiteDatabase = options.sqlite?.Database;
|
|
2688
|
+
let migrationPromise = null;
|
|
2689
|
+
if (!SQLiteDatabase) {
|
|
2690
|
+
throw new Error(
|
|
2691
|
+
"Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
const db = new SQLiteDatabase(dbPath);
|
|
2695
|
+
db.run(`
|
|
2696
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
2697
|
+
id TEXT PRIMARY KEY,
|
|
2698
|
+
timestamp INTEGER NOT NULL,
|
|
2699
|
+
model_id TEXT NOT NULL,
|
|
2700
|
+
base_url TEXT NOT NULL,
|
|
2701
|
+
request_id TEXT NOT NULL,
|
|
2702
|
+
cost REAL NOT NULL,
|
|
2703
|
+
sats_cost REAL NOT NULL,
|
|
2704
|
+
prompt_tokens INTEGER NOT NULL,
|
|
2705
|
+
completion_tokens INTEGER NOT NULL,
|
|
2706
|
+
total_tokens INTEGER NOT NULL,
|
|
2707
|
+
client TEXT,
|
|
2708
|
+
session_id TEXT,
|
|
2709
|
+
tags TEXT
|
|
2710
|
+
)
|
|
2711
|
+
`);
|
|
2712
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
|
|
2713
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
|
|
2714
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
|
|
2715
|
+
const appendOne = (entry) => {
|
|
2716
|
+
db.query(`
|
|
2717
|
+
INSERT OR REPLACE INTO ${tableName} (
|
|
2718
|
+
id, timestamp, model_id, base_url, request_id,
|
|
2719
|
+
cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
|
|
2720
|
+
client, session_id, tags
|
|
2721
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2722
|
+
`).run(
|
|
2723
|
+
entry.id,
|
|
2724
|
+
entry.timestamp,
|
|
2725
|
+
entry.modelId,
|
|
2726
|
+
normalizeBaseUrl3(entry.baseUrl),
|
|
2727
|
+
entry.requestId,
|
|
2728
|
+
entry.cost,
|
|
2729
|
+
entry.satsCost,
|
|
2730
|
+
entry.promptTokens,
|
|
2731
|
+
entry.completionTokens,
|
|
2732
|
+
entry.totalTokens,
|
|
2733
|
+
entry.client ?? null,
|
|
2734
|
+
entry.sessionId ?? null,
|
|
2735
|
+
JSON.stringify(entry.tags ?? [])
|
|
2736
|
+
);
|
|
2737
|
+
};
|
|
2738
|
+
const mapRow = (row) => ({
|
|
2739
|
+
id: row.id,
|
|
2740
|
+
timestamp: row.timestamp,
|
|
2741
|
+
modelId: row.model_id,
|
|
2742
|
+
baseUrl: row.base_url,
|
|
2743
|
+
requestId: row.request_id,
|
|
2744
|
+
cost: row.cost,
|
|
2745
|
+
satsCost: row.sats_cost,
|
|
2746
|
+
promptTokens: row.prompt_tokens,
|
|
2747
|
+
completionTokens: row.completion_tokens,
|
|
2748
|
+
totalTokens: row.total_tokens,
|
|
2749
|
+
client: row.client ?? void 0,
|
|
2750
|
+
sessionId: row.session_id ?? void 0,
|
|
2751
|
+
tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
|
|
2752
|
+
});
|
|
2753
|
+
const ensureMigrated = async () => {
|
|
2754
|
+
if (!legacyStorageDriver) return;
|
|
2755
|
+
if (!migrationPromise) {
|
|
2756
|
+
migrationPromise = (async () => {
|
|
2757
|
+
const migrated = await legacyStorageDriver.getItem(
|
|
2758
|
+
MIGRATION_MARKER_KEY3,
|
|
2759
|
+
false
|
|
2760
|
+
);
|
|
2761
|
+
if (migrated) return;
|
|
2762
|
+
const legacyEntries = await legacyStorageDriver.getItem(
|
|
2763
|
+
SDK_STORAGE_KEYS.USAGE_TRACKING,
|
|
2764
|
+
[]
|
|
2765
|
+
);
|
|
2766
|
+
if (legacyEntries.length > 0) {
|
|
2767
|
+
for (const entry of legacyEntries) {
|
|
2768
|
+
appendOne(entry);
|
|
2769
|
+
}
|
|
2770
|
+
await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
|
|
2771
|
+
}
|
|
2772
|
+
await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
|
|
2773
|
+
})();
|
|
2774
|
+
}
|
|
2775
|
+
await migrationPromise;
|
|
2776
|
+
};
|
|
2777
|
+
return {
|
|
2778
|
+
async migrate() {
|
|
2779
|
+
await ensureMigrated();
|
|
2780
|
+
},
|
|
2781
|
+
async append(entry) {
|
|
2782
|
+
await ensureMigrated();
|
|
2783
|
+
appendOne(entry);
|
|
2784
|
+
},
|
|
2785
|
+
async appendMany(entries) {
|
|
2786
|
+
await ensureMigrated();
|
|
2787
|
+
for (const entry of entries) {
|
|
2788
|
+
appendOne(entry);
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
async list(options2 = {}) {
|
|
2792
|
+
await ensureMigrated();
|
|
2793
|
+
const { sql, params } = buildWhereClause2(options2);
|
|
2794
|
+
const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
|
|
2795
|
+
const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
|
|
2796
|
+
let rows;
|
|
2797
|
+
if (typeof options2.limit === "number") {
|
|
2798
|
+
rows = db.query(query).all(...params, options2.limit);
|
|
2799
|
+
} else {
|
|
2800
|
+
rows = db.query(query).all(...params);
|
|
2801
|
+
}
|
|
2802
|
+
return rows.map(mapRow);
|
|
2803
|
+
},
|
|
2804
|
+
async count(options2 = {}) {
|
|
2805
|
+
const { sql, params } = buildWhereClause2(options2);
|
|
2806
|
+
const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
|
|
2807
|
+
const row = db.query(query).get(...params);
|
|
2808
|
+
return Number(row?.count ?? 0);
|
|
2809
|
+
},
|
|
2810
|
+
async deleteOlderThan(timestamp) {
|
|
2811
|
+
await ensureMigrated();
|
|
2812
|
+
const before = timestamp;
|
|
2813
|
+
const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
|
|
2814
|
+
return result.changes ?? 0;
|
|
2815
|
+
},
|
|
2816
|
+
async clear() {
|
|
2817
|
+
await ensureMigrated();
|
|
2818
|
+
db.query(`DELETE FROM ${tableName}`).run();
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// storage/usageTracking/memory.ts
|
|
2824
|
+
var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2635
2825
|
var matchesFilters2 = (entry, options = {}) => {
|
|
2636
2826
|
if (typeof options.before === "number" && entry.timestamp >= options.before) {
|
|
2637
2827
|
return false;
|
|
@@ -2642,7 +2832,7 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
2642
2832
|
if (options.modelId && entry.modelId !== options.modelId) {
|
|
2643
2833
|
return false;
|
|
2644
2834
|
}
|
|
2645
|
-
if (options.baseUrl &&
|
|
2835
|
+
if (options.baseUrl && normalizeBaseUrl4(entry.baseUrl) !== normalizeBaseUrl4(options.baseUrl)) {
|
|
2646
2836
|
return false;
|
|
2647
2837
|
}
|
|
2648
2838
|
if (options.sessionId && entry.sessionId !== options.sessionId) {
|
|
@@ -2656,18 +2846,18 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
2656
2846
|
var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
2657
2847
|
const store = /* @__PURE__ */ new Map();
|
|
2658
2848
|
for (const entry of seed) {
|
|
2659
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2849
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
|
|
2660
2850
|
}
|
|
2661
2851
|
return {
|
|
2662
2852
|
async migrate() {
|
|
2663
2853
|
return;
|
|
2664
2854
|
},
|
|
2665
2855
|
async append(entry) {
|
|
2666
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2856
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
|
|
2667
2857
|
},
|
|
2668
2858
|
async appendMany(entries) {
|
|
2669
2859
|
for (const entry of entries) {
|
|
2670
|
-
store.set(entry.id, { ...entry, baseUrl:
|
|
2860
|
+
store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
|
|
2671
2861
|
}
|
|
2672
2862
|
},
|
|
2673
2863
|
async list(options = {}) {
|
|
@@ -2695,20 +2885,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
2695
2885
|
}
|
|
2696
2886
|
};
|
|
2697
2887
|
};
|
|
2698
|
-
var
|
|
2699
|
-
var getCashuTokenBalance = (token) => {
|
|
2700
|
-
try {
|
|
2701
|
-
const decoded = getDecodedToken(token);
|
|
2702
|
-
const unitDivisor = decoded.unit === "msat" ? 1e3 : 1;
|
|
2703
|
-
let sum = 0;
|
|
2704
|
-
for (const proof of decoded.proofs) {
|
|
2705
|
-
sum += proof.amount / unitDivisor;
|
|
2706
|
-
}
|
|
2707
|
-
return sum;
|
|
2708
|
-
} catch {
|
|
2709
|
-
return 0;
|
|
2710
|
-
}
|
|
2711
|
-
};
|
|
2888
|
+
var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
2712
2889
|
var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
2713
2890
|
modelsFromAllProviders: {},
|
|
2714
2891
|
lastUsedModel: null,
|
|
@@ -2718,17 +2895,20 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2718
2895
|
mintsFromAllProviders: {},
|
|
2719
2896
|
infoFromAllProviders: {},
|
|
2720
2897
|
lastModelsUpdate: {},
|
|
2721
|
-
cachedTokens: [],
|
|
2722
2898
|
apiKeys: [],
|
|
2723
2899
|
childKeys: [],
|
|
2900
|
+
xcashuTokens: {},
|
|
2724
2901
|
routstr21Models: [],
|
|
2725
2902
|
lastRoutstr21ModelsUpdate: null,
|
|
2726
2903
|
cachedReceiveTokens: [],
|
|
2727
2904
|
clientIds: [],
|
|
2905
|
+
failedProviders: [],
|
|
2906
|
+
lastFailed: {},
|
|
2907
|
+
providersOnCooldown: [],
|
|
2728
2908
|
setModelsFromAllProviders: (value) => {
|
|
2729
2909
|
const normalized = {};
|
|
2730
2910
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
2731
|
-
normalized[
|
|
2911
|
+
normalized[normalizeBaseUrl5(baseUrl)] = models;
|
|
2732
2912
|
}
|
|
2733
2913
|
void driver.setItem(
|
|
2734
2914
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -2741,7 +2921,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2741
2921
|
set({ lastUsedModel: value });
|
|
2742
2922
|
},
|
|
2743
2923
|
setBaseUrlsList: (value) => {
|
|
2744
|
-
const normalized = value.map((url) =>
|
|
2924
|
+
const normalized = value.map((url) => normalizeBaseUrl5(url));
|
|
2745
2925
|
void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
|
|
2746
2926
|
set({ baseUrlsList: normalized });
|
|
2747
2927
|
},
|
|
@@ -2750,14 +2930,14 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2750
2930
|
set({ lastBaseUrlsUpdate: value });
|
|
2751
2931
|
},
|
|
2752
2932
|
setDisabledProviders: (value) => {
|
|
2753
|
-
const normalized = value.map((url) =>
|
|
2933
|
+
const normalized = value.map((url) => normalizeBaseUrl5(url));
|
|
2754
2934
|
void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
|
|
2755
2935
|
set({ disabledProviders: normalized });
|
|
2756
2936
|
},
|
|
2757
2937
|
setMintsFromAllProviders: (value) => {
|
|
2758
2938
|
const normalized = {};
|
|
2759
2939
|
for (const [baseUrl, mints] of Object.entries(value)) {
|
|
2760
|
-
normalized[
|
|
2940
|
+
normalized[normalizeBaseUrl5(baseUrl)] = mints.map(
|
|
2761
2941
|
(mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
|
|
2762
2942
|
);
|
|
2763
2943
|
}
|
|
@@ -2770,7 +2950,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2770
2950
|
setInfoFromAllProviders: (value) => {
|
|
2771
2951
|
const normalized = {};
|
|
2772
2952
|
for (const [baseUrl, info] of Object.entries(value)) {
|
|
2773
|
-
normalized[
|
|
2953
|
+
normalized[normalizeBaseUrl5(baseUrl)] = info;
|
|
2774
2954
|
}
|
|
2775
2955
|
void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
|
|
2776
2956
|
set({ infoFromAllProviders: normalized });
|
|
@@ -2778,30 +2958,17 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2778
2958
|
setLastModelsUpdate: (value) => {
|
|
2779
2959
|
const normalized = {};
|
|
2780
2960
|
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
2781
|
-
normalized[
|
|
2961
|
+
normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
|
|
2782
2962
|
}
|
|
2783
2963
|
void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
|
|
2784
2964
|
set({ lastModelsUpdate: normalized });
|
|
2785
2965
|
},
|
|
2786
|
-
setCachedTokens: (value) => {
|
|
2787
|
-
set((state) => {
|
|
2788
|
-
const updates = typeof value === "function" ? value(state.cachedTokens) : value;
|
|
2789
|
-
const normalized = updates.map((entry) => ({
|
|
2790
|
-
...entry,
|
|
2791
|
-
baseUrl: normalizeBaseUrl4(entry.baseUrl),
|
|
2792
|
-
balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
|
|
2793
|
-
lastUsed: entry.lastUsed ?? null
|
|
2794
|
-
}));
|
|
2795
|
-
void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
|
|
2796
|
-
return { cachedTokens: normalized };
|
|
2797
|
-
});
|
|
2798
|
-
},
|
|
2799
2966
|
setApiKeys: (value) => {
|
|
2800
2967
|
set((state) => {
|
|
2801
2968
|
const updates = typeof value === "function" ? value(state.apiKeys) : value;
|
|
2802
2969
|
const normalized = updates.map((entry) => ({
|
|
2803
2970
|
...entry,
|
|
2804
|
-
baseUrl:
|
|
2971
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
2805
2972
|
balance: entry.balance ?? 0,
|
|
2806
2973
|
lastUsed: entry.lastUsed ?? null
|
|
2807
2974
|
}));
|
|
@@ -2813,7 +2980,7 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2813
2980
|
set((state) => {
|
|
2814
2981
|
const updates = typeof value === "function" ? value(state.childKeys) : value;
|
|
2815
2982
|
const normalized = updates.map((entry) => ({
|
|
2816
|
-
parentBaseUrl:
|
|
2983
|
+
parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
|
|
2817
2984
|
childKey: entry.childKey,
|
|
2818
2985
|
balance: entry.balance ?? 0,
|
|
2819
2986
|
balanceLimit: entry.balanceLimit,
|
|
@@ -2824,6 +2991,30 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2824
2991
|
return { childKeys: normalized };
|
|
2825
2992
|
});
|
|
2826
2993
|
},
|
|
2994
|
+
setXcashuTokens: (value) => {
|
|
2995
|
+
const normalized = {};
|
|
2996
|
+
for (const [baseUrl, tokens] of Object.entries(value)) {
|
|
2997
|
+
normalized[normalizeBaseUrl5(baseUrl)] = tokens.map((entry) => ({
|
|
2998
|
+
...entry,
|
|
2999
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3000
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
3001
|
+
tryCount: entry.tryCount ?? 0
|
|
3002
|
+
}));
|
|
3003
|
+
}
|
|
3004
|
+
void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, normalized);
|
|
3005
|
+
set({ xcashuTokens: normalized });
|
|
3006
|
+
},
|
|
3007
|
+
updateXcashuTokenTryCount: (token, tryCount) => {
|
|
3008
|
+
const currentTokens = get().xcashuTokens;
|
|
3009
|
+
const updatedTokens = {};
|
|
3010
|
+
for (const [baseUrl, tokens] of Object.entries(currentTokens)) {
|
|
3011
|
+
updatedTokens[baseUrl] = tokens.map(
|
|
3012
|
+
(entry) => entry.token === token ? { ...entry, tryCount } : entry
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, updatedTokens);
|
|
3016
|
+
set({ xcashuTokens: updatedTokens });
|
|
3017
|
+
},
|
|
2827
3018
|
setRoutstr21Models: (value) => {
|
|
2828
3019
|
void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
|
|
2829
3020
|
set({ routstr21Models: value });
|
|
@@ -2853,6 +3044,71 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
|
|
|
2853
3044
|
void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
|
|
2854
3045
|
return { clientIds: normalized };
|
|
2855
3046
|
});
|
|
3047
|
+
},
|
|
3048
|
+
// ========== Failure Tracking ==========
|
|
3049
|
+
setFailedProviders: (value) => {
|
|
3050
|
+
const normalized = value.map((url) => normalizeBaseUrl5(url));
|
|
3051
|
+
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
3052
|
+
set({ failedProviders: normalized });
|
|
3053
|
+
},
|
|
3054
|
+
addFailedProvider: (baseUrl) => {
|
|
3055
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3056
|
+
const current = get().failedProviders;
|
|
3057
|
+
if (!current.includes(normalized)) {
|
|
3058
|
+
const updated = [...current, normalized];
|
|
3059
|
+
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
3060
|
+
set({ failedProviders: updated });
|
|
3061
|
+
}
|
|
3062
|
+
},
|
|
3063
|
+
removeFailedProvider: (baseUrl) => {
|
|
3064
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3065
|
+
const current = get().failedProviders;
|
|
3066
|
+
const updated = current.filter((url) => url !== normalized);
|
|
3067
|
+
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
3068
|
+
set({ failedProviders: updated });
|
|
3069
|
+
},
|
|
3070
|
+
setLastFailed: (value) => {
|
|
3071
|
+
const normalized = {};
|
|
3072
|
+
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3073
|
+
normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
|
|
3074
|
+
}
|
|
3075
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
3076
|
+
set({ lastFailed: normalized });
|
|
3077
|
+
},
|
|
3078
|
+
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
3079
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3080
|
+
const current = get().lastFailed;
|
|
3081
|
+
const updated = { ...current, [normalized]: timestamp };
|
|
3082
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
3083
|
+
set({ lastFailed: updated });
|
|
3084
|
+
},
|
|
3085
|
+
setProvidersOnCooldown: (value) => {
|
|
3086
|
+
const normalized = value.map((entry) => ({
|
|
3087
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3088
|
+
timestamp: entry.timestamp
|
|
3089
|
+
}));
|
|
3090
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
3091
|
+
set({ providersOnCooldown: normalized });
|
|
3092
|
+
},
|
|
3093
|
+
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
3094
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3095
|
+
const current = get().providersOnCooldown;
|
|
3096
|
+
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
3097
|
+
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
3098
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
3099
|
+
set({ providersOnCooldown: updated });
|
|
3100
|
+
}
|
|
3101
|
+
},
|
|
3102
|
+
removeProviderFromCooldown: (baseUrl) => {
|
|
3103
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3104
|
+
const current = get().providersOnCooldown;
|
|
3105
|
+
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
3106
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
3107
|
+
set({ providersOnCooldown: updated });
|
|
3108
|
+
},
|
|
3109
|
+
clearProvidersOnCooldown: () => {
|
|
3110
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, []);
|
|
3111
|
+
set({ providersOnCooldown: [] });
|
|
2856
3112
|
}
|
|
2857
3113
|
}));
|
|
2858
3114
|
var hydrateStoreFromDriver = async (store, driver) => {
|
|
@@ -2865,13 +3121,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
2865
3121
|
rawMints,
|
|
2866
3122
|
rawInfo,
|
|
2867
3123
|
rawLastModelsUpdate,
|
|
2868
|
-
rawCachedTokens,
|
|
2869
3124
|
rawApiKeys,
|
|
2870
3125
|
rawChildKeys,
|
|
3126
|
+
rawXcashuTokens,
|
|
2871
3127
|
rawRoutstr21Models,
|
|
2872
3128
|
rawLastRoutstr21ModelsUpdate,
|
|
2873
3129
|
rawCachedReceiveTokens,
|
|
2874
|
-
rawClientIds
|
|
3130
|
+
rawClientIds,
|
|
3131
|
+
rawFailedProviders,
|
|
3132
|
+
rawLastFailed,
|
|
3133
|
+
rawProvidersOnCooldown
|
|
2875
3134
|
] = await Promise.all([
|
|
2876
3135
|
driver.getItem(
|
|
2877
3136
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -2893,65 +3152,73 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
2893
3152
|
SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
|
|
2894
3153
|
{}
|
|
2895
3154
|
),
|
|
2896
|
-
driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
|
|
2897
3155
|
driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
|
|
2898
3156
|
driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
|
|
3157
|
+
driver.getItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, {}),
|
|
2899
3158
|
driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
|
|
2900
3159
|
driver.getItem(
|
|
2901
3160
|
SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
|
|
2902
3161
|
null
|
|
2903
3162
|
),
|
|
2904
3163
|
driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
|
|
2905
|
-
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
|
|
3164
|
+
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, []),
|
|
3165
|
+
driver.getItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, []),
|
|
3166
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_FAILED, {}),
|
|
3167
|
+
driver.getItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, [])
|
|
2906
3168
|
]);
|
|
2907
3169
|
const modelsFromAllProviders = Object.fromEntries(
|
|
2908
3170
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
2909
|
-
|
|
3171
|
+
normalizeBaseUrl5(baseUrl),
|
|
2910
3172
|
models
|
|
2911
3173
|
])
|
|
2912
3174
|
);
|
|
2913
|
-
const baseUrlsList = rawBaseUrls.map((url) =>
|
|
3175
|
+
const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl5(url));
|
|
2914
3176
|
const disabledProviders = rawDisabledProviders.map(
|
|
2915
|
-
(url) =>
|
|
3177
|
+
(url) => normalizeBaseUrl5(url)
|
|
2916
3178
|
);
|
|
2917
3179
|
const mintsFromAllProviders = Object.fromEntries(
|
|
2918
3180
|
Object.entries(rawMints).map(([baseUrl, mints]) => [
|
|
2919
|
-
|
|
3181
|
+
normalizeBaseUrl5(baseUrl),
|
|
2920
3182
|
mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
|
|
2921
3183
|
])
|
|
2922
3184
|
);
|
|
2923
3185
|
const infoFromAllProviders = Object.fromEntries(
|
|
2924
3186
|
Object.entries(rawInfo).map(([baseUrl, info]) => [
|
|
2925
|
-
|
|
3187
|
+
normalizeBaseUrl5(baseUrl),
|
|
2926
3188
|
info
|
|
2927
3189
|
])
|
|
2928
3190
|
);
|
|
2929
3191
|
const lastModelsUpdate = Object.fromEntries(
|
|
2930
3192
|
Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
|
|
2931
|
-
|
|
3193
|
+
normalizeBaseUrl5(baseUrl),
|
|
2932
3194
|
timestamp
|
|
2933
3195
|
])
|
|
2934
3196
|
);
|
|
2935
|
-
const cachedTokens = rawCachedTokens.map((entry) => ({
|
|
2936
|
-
...entry,
|
|
2937
|
-
baseUrl: normalizeBaseUrl4(entry.baseUrl),
|
|
2938
|
-
balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
|
|
2939
|
-
lastUsed: entry.lastUsed ?? null
|
|
2940
|
-
}));
|
|
2941
3197
|
const apiKeys = rawApiKeys.map((entry) => ({
|
|
2942
3198
|
...entry,
|
|
2943
|
-
baseUrl:
|
|
3199
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
2944
3200
|
balance: entry.balance ?? 0,
|
|
2945
3201
|
lastUsed: entry.lastUsed ?? null
|
|
2946
3202
|
}));
|
|
2947
3203
|
const childKeys = rawChildKeys.map((entry) => ({
|
|
2948
|
-
parentBaseUrl:
|
|
3204
|
+
parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
|
|
2949
3205
|
childKey: entry.childKey,
|
|
2950
3206
|
balance: entry.balance ?? 0,
|
|
2951
3207
|
balanceLimit: entry.balanceLimit,
|
|
2952
3208
|
validityDate: entry.validityDate,
|
|
2953
3209
|
createdAt: entry.createdAt ?? Date.now()
|
|
2954
3210
|
}));
|
|
3211
|
+
const xcashuTokens = Object.fromEntries(
|
|
3212
|
+
Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
|
|
3213
|
+
normalizeBaseUrl5(baseUrl),
|
|
3214
|
+
tokens.map((entry) => ({
|
|
3215
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3216
|
+
token: entry.token,
|
|
3217
|
+
createdAt: entry.createdAt ?? Date.now(),
|
|
3218
|
+
tryCount: entry.tryCount ?? 0
|
|
3219
|
+
}))
|
|
3220
|
+
])
|
|
3221
|
+
);
|
|
2955
3222
|
const routstr21Models = rawRoutstr21Models;
|
|
2956
3223
|
const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
|
|
2957
3224
|
const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
|
|
@@ -2965,6 +3232,17 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
2965
3232
|
createdAt: entry.createdAt ?? Date.now(),
|
|
2966
3233
|
lastUsed: entry.lastUsed ?? null
|
|
2967
3234
|
}));
|
|
3235
|
+
const failedProviders = rawFailedProviders.map((url) => normalizeBaseUrl5(url));
|
|
3236
|
+
const lastFailed = Object.fromEntries(
|
|
3237
|
+
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
3238
|
+
normalizeBaseUrl5(baseUrl),
|
|
3239
|
+
timestamp
|
|
3240
|
+
])
|
|
3241
|
+
);
|
|
3242
|
+
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
3243
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3244
|
+
timestamp: entry.timestamp
|
|
3245
|
+
}));
|
|
2968
3246
|
store.setState({
|
|
2969
3247
|
modelsFromAllProviders,
|
|
2970
3248
|
lastUsedModel,
|
|
@@ -2974,13 +3252,16 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
2974
3252
|
mintsFromAllProviders,
|
|
2975
3253
|
infoFromAllProviders,
|
|
2976
3254
|
lastModelsUpdate,
|
|
2977
|
-
cachedTokens,
|
|
2978
3255
|
apiKeys,
|
|
2979
3256
|
childKeys,
|
|
3257
|
+
xcashuTokens,
|
|
2980
3258
|
routstr21Models,
|
|
2981
3259
|
lastRoutstr21ModelsUpdate,
|
|
2982
3260
|
cachedReceiveTokens,
|
|
2983
|
-
clientIds
|
|
3261
|
+
clientIds,
|
|
3262
|
+
failedProviders,
|
|
3263
|
+
lastFailed,
|
|
3264
|
+
providersOnCooldown
|
|
2984
3265
|
});
|
|
2985
3266
|
};
|
|
2986
3267
|
var createSdkStore = ({
|
|
@@ -3047,7 +3328,7 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3047
3328
|
return defaultUsageTrackingDriver;
|
|
3048
3329
|
}
|
|
3049
3330
|
if (isBun3()) {
|
|
3050
|
-
defaultUsageTrackingDriver =
|
|
3331
|
+
defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
|
|
3051
3332
|
return defaultUsageTrackingDriver;
|
|
3052
3333
|
}
|
|
3053
3334
|
if (isNode()) {
|
|
@@ -3061,16 +3342,20 @@ var getDefaultUsageTrackingDriver = () => {
|
|
|
3061
3342
|
};
|
|
3062
3343
|
function createSSEParserTransform(onUsage, onResponseId) {
|
|
3063
3344
|
let buffer = "";
|
|
3345
|
+
let usageCaptured = false;
|
|
3346
|
+
let responseIdCaptured = false;
|
|
3064
3347
|
const maybeCaptureUsageFromJson = (jsonText) => {
|
|
3065
3348
|
try {
|
|
3066
3349
|
const data = JSON.parse(jsonText);
|
|
3067
3350
|
const responseId = data.id;
|
|
3068
3351
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
3069
3352
|
onResponseId?.(responseId.trim());
|
|
3353
|
+
responseIdCaptured = true;
|
|
3070
3354
|
}
|
|
3071
3355
|
const usage = extractUsageFromSSEJson(data);
|
|
3072
3356
|
if (usage) {
|
|
3073
3357
|
onUsage(usage);
|
|
3358
|
+
usageCaptured = true;
|
|
3074
3359
|
}
|
|
3075
3360
|
} catch {
|
|
3076
3361
|
}
|
|
@@ -3126,7 +3411,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
|
|
|
3126
3411
|
}
|
|
3127
3412
|
var TOPUP_MARGIN = 1.2;
|
|
3128
3413
|
var RoutstrClient = class {
|
|
3129
|
-
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
|
|
3414
|
+
constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
|
|
3130
3415
|
this.walletAdapter = walletAdapter;
|
|
3131
3416
|
this.storageAdapter = storageAdapter;
|
|
3132
3417
|
this.providerRegistry = providerRegistry;
|
|
@@ -3142,15 +3427,11 @@ var RoutstrClient = class {
|
|
|
3142
3427
|
this.balanceManager
|
|
3143
3428
|
);
|
|
3144
3429
|
this.streamProcessor = new StreamProcessor();
|
|
3145
|
-
this.providerManager = new ProviderManager(providerRegistry);
|
|
3146
3430
|
this.alertLevel = alertLevel;
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
} else {
|
|
3152
|
-
this.mode = mode;
|
|
3153
|
-
}
|
|
3431
|
+
this.mode = mode;
|
|
3432
|
+
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
3433
|
+
this.sdkStore = options.sdkStore;
|
|
3434
|
+
this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore);
|
|
3154
3435
|
}
|
|
3155
3436
|
cashuSpender;
|
|
3156
3437
|
balanceManager;
|
|
@@ -3159,6 +3440,8 @@ var RoutstrClient = class {
|
|
|
3159
3440
|
alertLevel;
|
|
3160
3441
|
mode;
|
|
3161
3442
|
debugLevel = "WARN";
|
|
3443
|
+
usageTrackingDriver;
|
|
3444
|
+
sdkStore;
|
|
3162
3445
|
/**
|
|
3163
3446
|
* Get the current client mode
|
|
3164
3447
|
*/
|
|
@@ -3228,11 +3511,13 @@ var RoutstrClient = class {
|
|
|
3228
3511
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3229
3512
|
token: prepared.tokenUsed,
|
|
3230
3513
|
baseUrl: prepared.baseUrlUsed,
|
|
3514
|
+
mintUrl: params.mintUrl,
|
|
3231
3515
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3232
3516
|
response: prepared.response,
|
|
3233
3517
|
modelId: prepared.modelId,
|
|
3234
3518
|
usage: prepared.capturedUsage,
|
|
3235
|
-
requestId: prepared.capturedResponseId
|
|
3519
|
+
requestId: prepared.capturedResponseId,
|
|
3520
|
+
clientApiKey: prepared.clientApiKey
|
|
3236
3521
|
});
|
|
3237
3522
|
prepared.response.satsSpent = satsSpent;
|
|
3238
3523
|
prepared.response.usage = prepared.capturedUsage;
|
|
@@ -3251,11 +3536,13 @@ var RoutstrClient = class {
|
|
|
3251
3536
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3252
3537
|
token: prepared.tokenUsed,
|
|
3253
3538
|
baseUrl: prepared.baseUrlUsed,
|
|
3539
|
+
mintUrl: params.mintUrl,
|
|
3254
3540
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3255
3541
|
response: prepared.response,
|
|
3256
3542
|
modelId: prepared.modelId,
|
|
3257
3543
|
usage: prepared.capturedUsage,
|
|
3258
|
-
requestId: prepared.capturedResponseId
|
|
3544
|
+
requestId: prepared.capturedResponseId,
|
|
3545
|
+
clientApiKey: prepared.clientApiKey
|
|
3259
3546
|
});
|
|
3260
3547
|
prepared.response.satsSpent = satsSpent;
|
|
3261
3548
|
res.end();
|
|
@@ -3271,11 +3558,13 @@ var RoutstrClient = class {
|
|
|
3271
3558
|
const satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3272
3559
|
token: prepared.tokenUsed,
|
|
3273
3560
|
baseUrl: prepared.baseUrlUsed,
|
|
3561
|
+
mintUrl: params.mintUrl,
|
|
3274
3562
|
initialTokenBalance: prepared.tokenBalanceInSats,
|
|
3275
3563
|
response: prepared.response,
|
|
3276
3564
|
modelId: prepared.modelId,
|
|
3277
3565
|
usage: prepared.capturedUsage,
|
|
3278
|
-
requestId: prepared.capturedResponseId
|
|
3566
|
+
requestId: prepared.capturedResponseId,
|
|
3567
|
+
clientApiKey: prepared.clientApiKey
|
|
3279
3568
|
});
|
|
3280
3569
|
prepared.response.satsSpent = satsSpent;
|
|
3281
3570
|
prepared.response.usage = prepared.capturedUsage;
|
|
@@ -3305,8 +3594,10 @@ var RoutstrClient = class {
|
|
|
3305
3594
|
headers = {},
|
|
3306
3595
|
baseUrl,
|
|
3307
3596
|
mintUrl,
|
|
3308
|
-
modelId
|
|
3597
|
+
modelId,
|
|
3598
|
+
clientApiKey: providedClientApiKey
|
|
3309
3599
|
} = params;
|
|
3600
|
+
const clientApiKey = providedClientApiKey ?? this._extractClientApiKey(headers);
|
|
3310
3601
|
await this._checkBalance();
|
|
3311
3602
|
let requiredSats = 1;
|
|
3312
3603
|
let selectedModel;
|
|
@@ -3328,7 +3619,6 @@ var RoutstrClient = class {
|
|
|
3328
3619
|
amount: requiredSats,
|
|
3329
3620
|
baseUrl
|
|
3330
3621
|
});
|
|
3331
|
-
this._log("DEBUG", token, baseUrl);
|
|
3332
3622
|
let requestBody = body;
|
|
3333
3623
|
if (body && typeof body === "object") {
|
|
3334
3624
|
const bodyObj = body;
|
|
@@ -3336,7 +3626,7 @@ var RoutstrClient = class {
|
|
|
3336
3626
|
requestBody = { ...bodyObj, stream: false };
|
|
3337
3627
|
}
|
|
3338
3628
|
}
|
|
3339
|
-
const baseHeaders = this._buildBaseHeaders(
|
|
3629
|
+
const baseHeaders = this._buildBaseHeaders();
|
|
3340
3630
|
const requestHeaders = this._withAuthHeader(baseHeaders, token);
|
|
3341
3631
|
const response = await this._makeRequest({
|
|
3342
3632
|
path,
|
|
@@ -3388,9 +3678,21 @@ var RoutstrClient = class {
|
|
|
3388
3678
|
tokenBalanceInSats,
|
|
3389
3679
|
modelId,
|
|
3390
3680
|
capturedUsage,
|
|
3391
|
-
capturedResponseId
|
|
3681
|
+
capturedResponseId,
|
|
3682
|
+
clientApiKey
|
|
3392
3683
|
};
|
|
3393
3684
|
}
|
|
3685
|
+
/**
|
|
3686
|
+
* Extract clientApiKey from Authorization Bearer token if present
|
|
3687
|
+
*/
|
|
3688
|
+
_extractClientApiKey(headers) {
|
|
3689
|
+
const authHeader = headers["Authorization"] || headers["authorization"];
|
|
3690
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
3691
|
+
const extractedKey = authHeader.slice(7);
|
|
3692
|
+
return extractedKey;
|
|
3693
|
+
}
|
|
3694
|
+
return void 0;
|
|
3695
|
+
}
|
|
3394
3696
|
/**
|
|
3395
3697
|
* Fetch AI response with streaming
|
|
3396
3698
|
*/
|
|
@@ -3494,6 +3796,7 @@ var RoutstrClient = class {
|
|
|
3494
3796
|
let satsSpent = await this._handlePostResponseBalanceUpdate({
|
|
3495
3797
|
token,
|
|
3496
3798
|
baseUrl: baseUrlUsed,
|
|
3799
|
+
mintUrl,
|
|
3497
3800
|
initialTokenBalance: tokenBalanceInSats,
|
|
3498
3801
|
fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
|
|
3499
3802
|
response,
|
|
@@ -3532,7 +3835,6 @@ var RoutstrClient = class {
|
|
|
3532
3835
|
try {
|
|
3533
3836
|
const url = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
3534
3837
|
if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
|
|
3535
|
-
this._log("DEBUG", "HEADERS,", headers);
|
|
3536
3838
|
const response = await fetch(url, {
|
|
3537
3839
|
method,
|
|
3538
3840
|
headers,
|
|
@@ -3601,8 +3903,6 @@ var RoutstrClient = class {
|
|
|
3601
3903
|
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
|
|
3602
3904
|
);
|
|
3603
3905
|
tryNextProvider = true;
|
|
3604
|
-
if (this.mode === "lazyrefund")
|
|
3605
|
-
this.storageAdapter.removeToken(baseUrl);
|
|
3606
3906
|
} else {
|
|
3607
3907
|
this._log(
|
|
3608
3908
|
"DEBUG",
|
|
@@ -3650,24 +3950,21 @@ var RoutstrClient = class {
|
|
|
3650
3950
|
);
|
|
3651
3951
|
}
|
|
3652
3952
|
}
|
|
3653
|
-
if (status === 402 && !tryNextProvider &&
|
|
3953
|
+
if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
|
|
3654
3954
|
this.storageAdapter.getApiKey(baseUrl);
|
|
3655
3955
|
let topupAmount = params.requiredSats;
|
|
3656
3956
|
try {
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
);
|
|
3663
|
-
currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
3664
|
-
} else if (this.mode === "lazyrefund") {
|
|
3665
|
-
const distribution = this.storageAdapter.getCachedTokenDistribution();
|
|
3666
|
-
const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
|
|
3667
|
-
currentBalance = tokenEntry?.amount ?? 0;
|
|
3668
|
-
}
|
|
3957
|
+
const currentBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3958
|
+
params.token,
|
|
3959
|
+
baseUrl
|
|
3960
|
+
);
|
|
3961
|
+
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
3669
3962
|
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
3670
3963
|
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
3964
|
+
this._log(
|
|
3965
|
+
"DEBUG",
|
|
3966
|
+
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
|
|
3967
|
+
);
|
|
3671
3968
|
} catch (e) {
|
|
3672
3969
|
this._log(
|
|
3673
3970
|
"WARN",
|
|
@@ -3797,61 +4094,49 @@ var RoutstrClient = class {
|
|
|
3797
4094
|
tryNextProvider = true;
|
|
3798
4095
|
}
|
|
3799
4096
|
}
|
|
4097
|
+
if (status === 401 && this.mode === "apikeys") {
|
|
4098
|
+
this._log(
|
|
4099
|
+
"DEBUG",
|
|
4100
|
+
`[RoutstrClient] _handleErrorResponse: Checking balance for ${baseUrl}, key preview=${token}`
|
|
4101
|
+
);
|
|
4102
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
4103
|
+
token,
|
|
4104
|
+
baseUrl
|
|
4105
|
+
);
|
|
4106
|
+
if (latestBalanceInfo.isInvalidApiKey) {
|
|
4107
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
4108
|
+
tryNextProvider = true;
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
3800
4111
|
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
3801
4112
|
this._log(
|
|
3802
4113
|
"DEBUG",
|
|
3803
4114
|
`[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
|
|
3804
4115
|
);
|
|
3805
|
-
if (this.mode === "
|
|
3806
|
-
try {
|
|
3807
|
-
const refundResult = await this.balanceManager.refund({
|
|
3808
|
-
mintUrl,
|
|
3809
|
-
baseUrl,
|
|
3810
|
-
token: params.token
|
|
3811
|
-
});
|
|
3812
|
-
this._log(
|
|
3813
|
-
"DEBUG",
|
|
3814
|
-
`[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
|
|
3815
|
-
);
|
|
3816
|
-
if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
|
|
3817
|
-
else
|
|
3818
|
-
throw new ProviderError(
|
|
3819
|
-
baseUrl,
|
|
3820
|
-
status,
|
|
3821
|
-
"refund failed",
|
|
3822
|
-
requestId
|
|
3823
|
-
);
|
|
3824
|
-
} catch (error) {
|
|
3825
|
-
throw new ProviderError(
|
|
3826
|
-
baseUrl,
|
|
3827
|
-
status,
|
|
3828
|
-
"Failed to refund token",
|
|
3829
|
-
requestId
|
|
3830
|
-
);
|
|
3831
|
-
}
|
|
3832
|
-
} else if (this.mode === "apikeys") {
|
|
4116
|
+
if (this.mode === "apikeys") {
|
|
3833
4117
|
this._log(
|
|
3834
4118
|
"DEBUG",
|
|
3835
4119
|
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
3836
4120
|
);
|
|
3837
|
-
const
|
|
4121
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3838
4122
|
token,
|
|
3839
4123
|
baseUrl
|
|
3840
4124
|
);
|
|
3841
4125
|
this._log(
|
|
3842
4126
|
"DEBUG",
|
|
3843
|
-
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${
|
|
4127
|
+
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
|
|
3844
4128
|
);
|
|
3845
4129
|
const refundResult = await this.balanceManager.refundApiKey({
|
|
3846
4130
|
mintUrl,
|
|
3847
4131
|
baseUrl,
|
|
3848
|
-
apiKey: token
|
|
4132
|
+
apiKey: token,
|
|
4133
|
+
forceRefund: true
|
|
3849
4134
|
});
|
|
3850
4135
|
this._log(
|
|
3851
4136
|
"DEBUG",
|
|
3852
4137
|
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
3853
4138
|
);
|
|
3854
|
-
if (!refundResult.success &&
|
|
4139
|
+
if (!refundResult.success && latestBalanceInfo.amount > 0) {
|
|
3855
4140
|
throw new ProviderError(
|
|
3856
4141
|
baseUrl,
|
|
3857
4142
|
status,
|
|
@@ -3927,12 +4212,14 @@ var RoutstrClient = class {
|
|
|
3927
4212
|
const {
|
|
3928
4213
|
token,
|
|
3929
4214
|
baseUrl,
|
|
4215
|
+
mintUrl,
|
|
3930
4216
|
initialTokenBalance,
|
|
3931
4217
|
fallbackSatsSpent,
|
|
3932
4218
|
response,
|
|
3933
4219
|
modelId,
|
|
3934
4220
|
usage,
|
|
3935
|
-
requestId
|
|
4221
|
+
requestId,
|
|
4222
|
+
clientApiKey
|
|
3936
4223
|
} = params;
|
|
3937
4224
|
let satsSpent = initialTokenBalance;
|
|
3938
4225
|
if (this.mode === "xcashu" && response) {
|
|
@@ -3940,19 +4227,14 @@ var RoutstrClient = class {
|
|
|
3940
4227
|
if (refundToken) {
|
|
3941
4228
|
try {
|
|
3942
4229
|
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
3943
|
-
|
|
4230
|
+
if (receiveResult.success) {
|
|
4231
|
+
this.storageAdapter.removeXcashuToken(baseUrl, token);
|
|
4232
|
+
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
4233
|
+
}
|
|
3944
4234
|
} catch (error) {
|
|
3945
4235
|
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
3946
4236
|
}
|
|
3947
4237
|
}
|
|
3948
|
-
} else if (this.mode === "lazyrefund") {
|
|
3949
|
-
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3950
|
-
token,
|
|
3951
|
-
baseUrl
|
|
3952
|
-
);
|
|
3953
|
-
const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
|
|
3954
|
-
this.storageAdapter.updateTokenBalance(baseUrl, latestTokenBalance);
|
|
3955
|
-
satsSpent = initialTokenBalance - latestTokenBalance;
|
|
3956
4238
|
} else if (this.mode === "apikeys") {
|
|
3957
4239
|
try {
|
|
3958
4240
|
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
@@ -3987,8 +4269,17 @@ var RoutstrClient = class {
|
|
|
3987
4269
|
modelId,
|
|
3988
4270
|
satsSpent,
|
|
3989
4271
|
usage,
|
|
3990
|
-
requestId
|
|
4272
|
+
requestId,
|
|
4273
|
+
clientApiKey
|
|
3991
4274
|
});
|
|
4275
|
+
(async () => {
|
|
4276
|
+
try {
|
|
4277
|
+
const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
|
|
4278
|
+
this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
|
|
4279
|
+
} catch (error) {
|
|
4280
|
+
this._log("ERROR", "Failed to refund providers:", error);
|
|
4281
|
+
}
|
|
4282
|
+
})();
|
|
3992
4283
|
return satsSpent;
|
|
3993
4284
|
}
|
|
3994
4285
|
async _trackResponseUsage(params) {
|
|
@@ -3999,7 +4290,8 @@ var RoutstrClient = class {
|
|
|
3999
4290
|
modelId,
|
|
4000
4291
|
satsSpent,
|
|
4001
4292
|
usage: providedUsage,
|
|
4002
|
-
requestId: providedRequestId
|
|
4293
|
+
requestId: providedRequestId,
|
|
4294
|
+
clientApiKey
|
|
4003
4295
|
} = params;
|
|
4004
4296
|
if (!response || !modelId) {
|
|
4005
4297
|
return;
|
|
@@ -4026,13 +4318,14 @@ var RoutstrClient = class {
|
|
|
4026
4318
|
return;
|
|
4027
4319
|
}
|
|
4028
4320
|
const finalRequestId = requestId || "unknown";
|
|
4029
|
-
const store = await getDefaultSdkStore();
|
|
4321
|
+
const store = this.sdkStore ?? await getDefaultSdkStore();
|
|
4030
4322
|
const state = store.getState();
|
|
4323
|
+
const matchKey = clientApiKey ?? token;
|
|
4031
4324
|
const matchingClient = state.clientIds.find(
|
|
4032
|
-
(client) => client.apiKey ===
|
|
4325
|
+
(client) => client.apiKey === matchKey
|
|
4033
4326
|
);
|
|
4034
4327
|
const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
|
|
4035
|
-
const usageTracking = getDefaultUsageTrackingDriver();
|
|
4328
|
+
const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
|
|
4036
4329
|
const entry = {
|
|
4037
4330
|
id: entryId,
|
|
4038
4331
|
timestamp: Date.now(),
|
|
@@ -4107,11 +4400,11 @@ var RoutstrClient = class {
|
|
|
4107
4400
|
return estimatedCosts;
|
|
4108
4401
|
}
|
|
4109
4402
|
/**
|
|
4110
|
-
* Get pending
|
|
4403
|
+
* Get pending API key amount
|
|
4111
4404
|
*/
|
|
4112
4405
|
_getPendingCashuTokenAmount() {
|
|
4113
|
-
const
|
|
4114
|
-
return
|
|
4406
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
4407
|
+
return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
|
|
4115
4408
|
}
|
|
4116
4409
|
/**
|
|
4117
4410
|
* Handle errors and notify callbacks
|
|
@@ -4258,8 +4551,8 @@ var RoutstrClient = class {
|
|
|
4258
4551
|
const spendResult = await this.cashuSpender.spend({
|
|
4259
4552
|
mintUrl,
|
|
4260
4553
|
amount,
|
|
4261
|
-
baseUrl:
|
|
4262
|
-
reuseToken:
|
|
4554
|
+
baseUrl: "",
|
|
4555
|
+
reuseToken: false
|
|
4263
4556
|
});
|
|
4264
4557
|
if (!spendResult.token) {
|
|
4265
4558
|
this._log(
|
|
@@ -4272,6 +4565,7 @@ var RoutstrClient = class {
|
|
|
4272
4565
|
"DEBUG",
|
|
4273
4566
|
`[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
|
|
4274
4567
|
);
|
|
4568
|
+
this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
|
|
4275
4569
|
}
|
|
4276
4570
|
return {
|
|
4277
4571
|
token: spendResult.token,
|