@routstr/sdk 0.2.6 → 0.2.8
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 +16 -2
- package/dist/client/index.d.ts +16 -2
- package/dist/client/index.js +342 -87
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +342 -87
- 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 +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +347 -91
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +347 -91
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.d.mts +5 -2
- package/dist/storage/index.d.ts +5 -2
- package/dist/storage/index.js +95 -4
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +95 -4
- package/dist/storage/index.mjs.map +1 -1
- package/dist/{store-C5lnyX8k.d.mts → store-DGeLPv9E.d.mts} +21 -0
- package/dist/{store-BJlwiDX5.d.ts → store-h7m23ffq.d.ts} +21 -0
- package/dist/wallet/index.d.mts +11 -4
- package/dist/wallet/index.d.ts +11 -4
- package/dist/wallet/index.js +83 -41
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +83 -41
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var cashuTs = require('@cashu/cashu-ts');
|
|
3
4
|
var vanilla = require('zustand/vanilla');
|
|
4
5
|
var stream = require('stream');
|
|
5
6
|
|
|
@@ -101,8 +102,6 @@ function selectMintWithBalance(balances, units, amount, excludeMints = []) {
|
|
|
101
102
|
}
|
|
102
103
|
return { selectedMintUrl: null, selectedMintBalance: 0 };
|
|
103
104
|
}
|
|
104
|
-
|
|
105
|
-
// wallet/CashuSpender.ts
|
|
106
105
|
var CashuSpender = class {
|
|
107
106
|
constructor(walletAdapter, storageAdapter, _providerRegistry, balanceManager) {
|
|
108
107
|
this.walletAdapter = walletAdapter;
|
|
@@ -113,23 +112,43 @@ var CashuSpender = class {
|
|
|
113
112
|
_isBusy = false;
|
|
114
113
|
debugLevel = "WARN";
|
|
115
114
|
async receiveToken(token) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
try {
|
|
116
|
+
const result = await this.walletAdapter.receiveToken(token);
|
|
117
|
+
return result;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
120
|
+
if (errorMessage.includes("Failed to fetch mint")) {
|
|
121
|
+
const cachedTokens = this.storageAdapter.getCachedReceiveTokens();
|
|
122
|
+
const existingIndex = cachedTokens.findIndex((t) => t.token === token);
|
|
123
|
+
if (existingIndex === -1) {
|
|
124
|
+
const { amount: amount2, unit: unit2 } = this._decodeTokenAmount(token);
|
|
125
|
+
this.storageAdapter.setCachedReceiveTokens([
|
|
126
|
+
...cachedTokens,
|
|
127
|
+
{
|
|
128
|
+
token,
|
|
129
|
+
amount: amount2,
|
|
130
|
+
unit: unit2,
|
|
131
|
+
createdAt: Date.now()
|
|
132
|
+
}
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
130
135
|
}
|
|
136
|
+
const { amount, unit } = this._decodeTokenAmount(token);
|
|
137
|
+
return { success: false, amount, unit, message: errorMessage };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
_decodeTokenAmount(token) {
|
|
141
|
+
try {
|
|
142
|
+
const decoded = cashuTs.getDecodedToken(token);
|
|
143
|
+
const amount = decoded.proofs.reduce(
|
|
144
|
+
(acc, proof) => acc + proof.amount,
|
|
145
|
+
0
|
|
146
|
+
);
|
|
147
|
+
const unit = decoded.unit || "sat";
|
|
148
|
+
return { amount, unit };
|
|
149
|
+
} catch {
|
|
150
|
+
return { amount: 0, unit: "sat" };
|
|
131
151
|
}
|
|
132
|
-
return result;
|
|
133
152
|
}
|
|
134
153
|
async _getBalanceState() {
|
|
135
154
|
if (this.balanceManager) {
|
|
@@ -449,8 +468,9 @@ var CashuSpender = class {
|
|
|
449
468
|
return null;
|
|
450
469
|
}
|
|
451
470
|
/**
|
|
452
|
-
* Refund all xcashu tokens from storage
|
|
453
|
-
*
|
|
471
|
+
* Refund all xcashu tokens from storage by calling the provider's refund endpoint.
|
|
472
|
+
* The xcashu token acts as an API key to claim the refund, and the response contains
|
|
473
|
+
* the actual refunded Cashu token which is then received into the wallet.
|
|
454
474
|
* @param mintUrl - The mint URL for receiving tokens
|
|
455
475
|
* @param excludeBaseUrls - Base URLs to exclude from refund (optional)
|
|
456
476
|
* @returns Results for each xcashu token refund attempt
|
|
@@ -463,7 +483,20 @@ var CashuSpender = class {
|
|
|
463
483
|
if (excludedUrls.has(baseUrl)) continue;
|
|
464
484
|
for (const xcashuToken of tokens) {
|
|
465
485
|
try {
|
|
466
|
-
|
|
486
|
+
if (!this.balanceManager) {
|
|
487
|
+
throw new Error("BalanceManager not available for xcashu refund");
|
|
488
|
+
}
|
|
489
|
+
const fetchResult = await this.balanceManager.fetchRefundToken(
|
|
490
|
+
baseUrl,
|
|
491
|
+
xcashuToken.token,
|
|
492
|
+
true
|
|
493
|
+
);
|
|
494
|
+
if (!fetchResult.success || !fetchResult.token) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
fetchResult.error || "Failed to fetch refund token from provider"
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
const receiveResult = await this.receiveToken(fetchResult.token);
|
|
467
500
|
if (receiveResult.success) {
|
|
468
501
|
this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
|
|
469
502
|
results.push({
|
|
@@ -478,7 +511,10 @@ var CashuSpender = class {
|
|
|
478
511
|
} else {
|
|
479
512
|
const currentTryCount = xcashuToken.tryCount ?? 0;
|
|
480
513
|
const newTryCount = currentTryCount + 1;
|
|
481
|
-
this.storageAdapter.updateXcashuTokenTryCount(
|
|
514
|
+
this.storageAdapter.updateXcashuTokenTryCount(
|
|
515
|
+
xcashuToken.token,
|
|
516
|
+
newTryCount
|
|
517
|
+
);
|
|
482
518
|
results.push({
|
|
483
519
|
baseUrl,
|
|
484
520
|
token: xcashuToken.token,
|
|
@@ -487,13 +523,16 @@ var CashuSpender = class {
|
|
|
487
523
|
});
|
|
488
524
|
this._log(
|
|
489
525
|
"DEBUG",
|
|
490
|
-
`[CashuSpender] refundXcashuTokens: Failed to refund
|
|
526
|
+
`[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}: ${receiveResult.message}`
|
|
491
527
|
);
|
|
492
528
|
}
|
|
493
529
|
} catch (error) {
|
|
494
530
|
const currentTryCount = xcashuToken.tryCount ?? 0;
|
|
495
531
|
const newTryCount = currentTryCount + 1;
|
|
496
|
-
this.storageAdapter.updateXcashuTokenTryCount(
|
|
532
|
+
this.storageAdapter.updateXcashuTokenTryCount(
|
|
533
|
+
xcashuToken.token,
|
|
534
|
+
newTryCount
|
|
535
|
+
);
|
|
497
536
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
498
537
|
results.push({
|
|
499
538
|
baseUrl,
|
|
@@ -530,7 +569,10 @@ var CashuSpender = class {
|
|
|
530
569
|
if (refundResult.success) {
|
|
531
570
|
this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
|
|
532
571
|
} else {
|
|
533
|
-
this.storageAdapter.updateApiKeyBalance(
|
|
572
|
+
this.storageAdapter.updateApiKeyBalance(
|
|
573
|
+
apiKeyEntry.baseUrl,
|
|
574
|
+
apiKeyEntry.amount
|
|
575
|
+
);
|
|
534
576
|
}
|
|
535
577
|
results.push({
|
|
536
578
|
baseUrl: apiKeyEntry.baseUrl,
|
|
@@ -668,7 +710,7 @@ var BalanceManager = class {
|
|
|
668
710
|
}
|
|
669
711
|
let fetchResult;
|
|
670
712
|
try {
|
|
671
|
-
fetchResult = await this.
|
|
713
|
+
fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
|
|
672
714
|
if (!fetchResult.success) {
|
|
673
715
|
return {
|
|
674
716
|
success: false,
|
|
@@ -696,6 +738,7 @@ var BalanceManager = class {
|
|
|
696
738
|
return {
|
|
697
739
|
success: receiveResult.success,
|
|
698
740
|
refundedAmount: totalAmountMsat,
|
|
741
|
+
message: receiveResult.message,
|
|
699
742
|
requestId: fetchResult.requestId
|
|
700
743
|
};
|
|
701
744
|
} catch (error) {
|
|
@@ -704,9 +747,9 @@ var BalanceManager = class {
|
|
|
704
747
|
}
|
|
705
748
|
}
|
|
706
749
|
/**
|
|
707
|
-
* Fetch refund token from provider API using API key authentication
|
|
750
|
+
* Fetch refund token from provider API using API key (or xcashu token) authentication
|
|
708
751
|
*/
|
|
709
|
-
async
|
|
752
|
+
async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
|
|
710
753
|
if (!baseUrl) {
|
|
711
754
|
return {
|
|
712
755
|
success: false,
|
|
@@ -720,12 +763,17 @@ var BalanceManager = class {
|
|
|
720
763
|
controller.abort();
|
|
721
764
|
}, 6e4);
|
|
722
765
|
try {
|
|
766
|
+
const headers = {
|
|
767
|
+
"Content-Type": "application/json"
|
|
768
|
+
};
|
|
769
|
+
if (xCashu) {
|
|
770
|
+
headers["X-Cashu"] = apiKeyOrToken;
|
|
771
|
+
} else {
|
|
772
|
+
headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
|
|
773
|
+
}
|
|
723
774
|
const response = await fetch(url, {
|
|
724
775
|
method: "POST",
|
|
725
|
-
headers
|
|
726
|
-
Authorization: `Bearer ${apiKey}`,
|
|
727
|
-
"Content-Type": "application/json"
|
|
728
|
-
},
|
|
776
|
+
headers,
|
|
729
777
|
signal: controller.signal
|
|
730
778
|
});
|
|
731
779
|
clearTimeout(timeoutId);
|
|
@@ -746,10 +794,7 @@ var BalanceManager = class {
|
|
|
746
794
|
};
|
|
747
795
|
} catch (error) {
|
|
748
796
|
clearTimeout(timeoutId);
|
|
749
|
-
console.error(
|
|
750
|
-
"[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
|
|
751
|
-
error
|
|
752
|
-
);
|
|
797
|
+
console.error("[BalanceManager.fetchRefundToken] Fetch error", error);
|
|
753
798
|
if (error instanceof Error) {
|
|
754
799
|
if (error.name === "AbortError") {
|
|
755
800
|
return {
|
|
@@ -796,11 +841,7 @@ var BalanceManager = class {
|
|
|
796
841
|
};
|
|
797
842
|
}
|
|
798
843
|
cashuToken = tokenResult.token;
|
|
799
|
-
const topUpResult = await this._postTopUp(
|
|
800
|
-
baseUrl,
|
|
801
|
-
apiKey,
|
|
802
|
-
cashuToken
|
|
803
|
-
);
|
|
844
|
+
const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
|
|
804
845
|
requestId = topUpResult.requestId;
|
|
805
846
|
console.log(topUpResult);
|
|
806
847
|
if (!topUpResult.success) {
|
|
@@ -1166,7 +1207,7 @@ var BalanceManager = class {
|
|
|
1166
1207
|
console.log(response.status);
|
|
1167
1208
|
const data = await response.json();
|
|
1168
1209
|
console.log("FAILED ", data);
|
|
1169
|
-
const isInvalidApiKey = response.status === 401 && data?.code === "invalid_api_key" && data?.message?.includes("proofs already spent");
|
|
1210
|
+
const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
|
|
1170
1211
|
return {
|
|
1171
1212
|
amount: -1,
|
|
1172
1213
|
reserved: data.reserved ?? 0,
|
|
@@ -1608,8 +1649,13 @@ function isInsecureHttpUrl(url) {
|
|
|
1608
1649
|
return url.startsWith("http://");
|
|
1609
1650
|
}
|
|
1610
1651
|
var ProviderManager = class _ProviderManager {
|
|
1611
|
-
constructor(providerRegistry) {
|
|
1652
|
+
constructor(providerRegistry, store) {
|
|
1612
1653
|
this.providerRegistry = providerRegistry;
|
|
1654
|
+
this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1655
|
+
if (store) {
|
|
1656
|
+
this.store = store;
|
|
1657
|
+
this.hydrateFromStore();
|
|
1658
|
+
}
|
|
1613
1659
|
}
|
|
1614
1660
|
failedProviders = /* @__PURE__ */ new Set();
|
|
1615
1661
|
/** Track when each provider last failed (provider URL -> timestamp) */
|
|
@@ -1618,14 +1664,57 @@ var ProviderManager = class _ProviderManager {
|
|
|
1618
1664
|
providersOnCoolDown = [];
|
|
1619
1665
|
/** Cooldown duration in milliseconds (5 minutes) */
|
|
1620
1666
|
static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
|
|
1667
|
+
/** Optional persistent store for failure tracking */
|
|
1668
|
+
store = null;
|
|
1669
|
+
/** Instance ID for debugging */
|
|
1670
|
+
instanceId;
|
|
1671
|
+
/**
|
|
1672
|
+
* Hydrate in-memory state from persistent store
|
|
1673
|
+
*/
|
|
1674
|
+
hydrateFromStore() {
|
|
1675
|
+
if (!this.store) return;
|
|
1676
|
+
const state = this.store.getState();
|
|
1677
|
+
this.failedProviders = new Set(state.failedProviders);
|
|
1678
|
+
this.lastFailed = new Map(Object.entries(state.lastFailed));
|
|
1679
|
+
const now = Date.now();
|
|
1680
|
+
this.providersOnCoolDown = state.providersOnCooldown.filter(
|
|
1681
|
+
(entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
|
|
1682
|
+
).map((entry) => [entry.baseUrl, entry.timestamp]);
|
|
1683
|
+
console.log(`[ProviderManager:${this.instanceId}] Hydrated from store:`);
|
|
1684
|
+
console.log(` failedProviders: ${this.failedProviders.size}`);
|
|
1685
|
+
console.log(` lastFailed: ${this.lastFailed.size}`);
|
|
1686
|
+
console.log(` providersOnCooldown: ${this.providersOnCoolDown.length}`);
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Get instance ID for debugging
|
|
1690
|
+
*/
|
|
1691
|
+
getInstanceId() {
|
|
1692
|
+
return this.instanceId;
|
|
1693
|
+
}
|
|
1621
1694
|
/**
|
|
1622
1695
|
* Clean up expired cooldown entries
|
|
1623
1696
|
*/
|
|
1624
1697
|
cleanupExpiredCooldowns() {
|
|
1625
1698
|
const now = Date.now();
|
|
1699
|
+
const before = this.providersOnCoolDown.length;
|
|
1626
1700
|
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1627
|
-
([, timestamp]) =>
|
|
1701
|
+
([url, timestamp]) => {
|
|
1702
|
+
const age = now - timestamp;
|
|
1703
|
+
const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
|
|
1704
|
+
if (isExpired) {
|
|
1705
|
+
console.log(
|
|
1706
|
+
`[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
return !isExpired;
|
|
1710
|
+
}
|
|
1628
1711
|
);
|
|
1712
|
+
const after = this.providersOnCoolDown.length;
|
|
1713
|
+
if (before !== after) {
|
|
1714
|
+
console.log(
|
|
1715
|
+
`[cleanupExpiredCooldowns:${this.instanceId}] Cleaned up ${before - after} expired cooldown(s), ${after} remaining`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1629
1718
|
}
|
|
1630
1719
|
/**
|
|
1631
1720
|
* Get the cooldown duration in milliseconds
|
|
@@ -1638,7 +1727,8 @@ var ProviderManager = class _ProviderManager {
|
|
|
1638
1727
|
*/
|
|
1639
1728
|
isOnCooldown(baseUrl) {
|
|
1640
1729
|
this.cleanupExpiredCooldowns();
|
|
1641
|
-
|
|
1730
|
+
const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
|
|
1731
|
+
return result;
|
|
1642
1732
|
}
|
|
1643
1733
|
/**
|
|
1644
1734
|
* Get all providers currently on cooldown
|
|
@@ -1652,6 +1742,9 @@ var ProviderManager = class _ProviderManager {
|
|
|
1652
1742
|
*/
|
|
1653
1743
|
resetFailedProviders() {
|
|
1654
1744
|
this.failedProviders.clear();
|
|
1745
|
+
if (this.store) {
|
|
1746
|
+
this.store.getState().setFailedProviders([]);
|
|
1747
|
+
}
|
|
1655
1748
|
}
|
|
1656
1749
|
/**
|
|
1657
1750
|
* Get the last failed timestamp for a provider
|
|
@@ -1672,13 +1765,62 @@ var ProviderManager = class _ProviderManager {
|
|
|
1672
1765
|
markFailed(baseUrl) {
|
|
1673
1766
|
const now = Date.now();
|
|
1674
1767
|
const lastFailure = this.lastFailed.get(baseUrl);
|
|
1768
|
+
console.log(`[markFailed:${this.instanceId}] baseUrl: ${baseUrl}`);
|
|
1769
|
+
console.log(
|
|
1770
|
+
`[markFailed:${this.instanceId}] lastFailure from map: ${lastFailure}`
|
|
1771
|
+
);
|
|
1772
|
+
console.log(
|
|
1773
|
+
`[markFailed:${this.instanceId}] current timestamp (now): ${now}`
|
|
1774
|
+
);
|
|
1775
|
+
console.log(
|
|
1776
|
+
`[markFailed:${this.instanceId}] COOLDOWN_DURATION_MS: ${_ProviderManager.COOLDOWN_DURATION_MS}`
|
|
1777
|
+
);
|
|
1778
|
+
if (lastFailure !== void 0) {
|
|
1779
|
+
const timeSinceLastFailure = now - lastFailure;
|
|
1780
|
+
console.log(
|
|
1781
|
+
`[markFailed:${this.instanceId}] timeSinceLastFailure: ${timeSinceLastFailure}ms`
|
|
1782
|
+
);
|
|
1783
|
+
console.log(
|
|
1784
|
+
`[markFailed:${this.instanceId}] isWithinCooldownWindow: ${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1675
1787
|
this.lastFailed.set(baseUrl, now);
|
|
1676
1788
|
this.failedProviders.add(baseUrl);
|
|
1789
|
+
if (this.store) {
|
|
1790
|
+
this.store.getState().setLastFailedTimestamp(baseUrl, now);
|
|
1791
|
+
this.store.getState().addFailedProvider(baseUrl);
|
|
1792
|
+
}
|
|
1793
|
+
console.log(
|
|
1794
|
+
`[markFailed:${this.instanceId}] Updated lastFailed map for ${baseUrl} to ${now}`
|
|
1795
|
+
);
|
|
1796
|
+
console.log(
|
|
1797
|
+
`[markFailed:${this.instanceId}] failedProviders set size: ${this.failedProviders.size}`
|
|
1798
|
+
);
|
|
1677
1799
|
if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
|
|
1800
|
+
console.log(
|
|
1801
|
+
`[markFailed:${this.instanceId}] Second failure detected within cooldown window for ${baseUrl}`
|
|
1802
|
+
);
|
|
1678
1803
|
if (!this.isOnCooldown(baseUrl)) {
|
|
1679
1804
|
this.providersOnCoolDown.push([baseUrl, now]);
|
|
1805
|
+
if (this.store) {
|
|
1806
|
+
this.store.getState().addProviderOnCooldown(baseUrl, now);
|
|
1807
|
+
}
|
|
1808
|
+
console.log(
|
|
1809
|
+
`[markFailed:${this.instanceId}] Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
|
|
1810
|
+
);
|
|
1811
|
+
} else {
|
|
1680
1812
|
console.log(
|
|
1681
|
-
`Provider ${baseUrl}
|
|
1813
|
+
`[markFailed:${this.instanceId}] Provider ${baseUrl} is already on cooldown`
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
} else {
|
|
1817
|
+
if (lastFailure === void 0) {
|
|
1818
|
+
console.log(
|
|
1819
|
+
`[markFailed:${this.instanceId}] First failure for ${baseUrl} - not adding to cooldown yet`
|
|
1820
|
+
);
|
|
1821
|
+
} else {
|
|
1822
|
+
console.log(
|
|
1823
|
+
`[markFailed:${this.instanceId}] Failure outside cooldown window for ${baseUrl} (timeSinceLastFailure: ${now - lastFailure}ms)`
|
|
1682
1824
|
);
|
|
1683
1825
|
}
|
|
1684
1826
|
}
|
|
@@ -1690,18 +1832,27 @@ var ProviderManager = class _ProviderManager {
|
|
|
1690
1832
|
this.providersOnCoolDown = this.providersOnCoolDown.filter(
|
|
1691
1833
|
([url]) => url !== baseUrl
|
|
1692
1834
|
);
|
|
1835
|
+
if (this.store) {
|
|
1836
|
+
this.store.getState().removeProviderFromCooldown(baseUrl);
|
|
1837
|
+
}
|
|
1693
1838
|
}
|
|
1694
1839
|
/**
|
|
1695
1840
|
* Clear all cooldown tracking
|
|
1696
1841
|
*/
|
|
1697
1842
|
clearCooldowns() {
|
|
1698
1843
|
this.providersOnCoolDown = [];
|
|
1844
|
+
if (this.store) {
|
|
1845
|
+
this.store.getState().clearProvidersOnCooldown();
|
|
1846
|
+
}
|
|
1699
1847
|
}
|
|
1700
1848
|
/**
|
|
1701
1849
|
* Clear all failure tracking (lastFailed timestamps)
|
|
1702
1850
|
*/
|
|
1703
1851
|
clearFailureHistory() {
|
|
1704
1852
|
this.lastFailed.clear();
|
|
1853
|
+
if (this.store) {
|
|
1854
|
+
this.store.getState().setLastFailed({});
|
|
1855
|
+
}
|
|
1705
1856
|
}
|
|
1706
1857
|
/**
|
|
1707
1858
|
* Check if a provider has failed
|
|
@@ -2123,7 +2274,10 @@ var SDK_STORAGE_KEYS = {
|
|
|
2123
2274
|
LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
|
|
2124
2275
|
CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
|
|
2125
2276
|
USAGE_TRACKING: "usage_tracking",
|
|
2126
|
-
CLIENT_IDS: "client_ids"
|
|
2277
|
+
CLIENT_IDS: "client_ids",
|
|
2278
|
+
FAILED_PROVIDERS: "failed_providers",
|
|
2279
|
+
LAST_FAILED: "last_failed",
|
|
2280
|
+
PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
|
|
2127
2281
|
};
|
|
2128
2282
|
|
|
2129
2283
|
// storage/usageTracking/indexedDB.ts
|
|
@@ -2770,6 +2924,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
2770
2924
|
lastRoutstr21ModelsUpdate: null,
|
|
2771
2925
|
cachedReceiveTokens: [],
|
|
2772
2926
|
clientIds: [],
|
|
2927
|
+
failedProviders: [],
|
|
2928
|
+
lastFailed: {},
|
|
2929
|
+
providersOnCooldown: [],
|
|
2773
2930
|
setModelsFromAllProviders: (value) => {
|
|
2774
2931
|
const normalized = {};
|
|
2775
2932
|
for (const [baseUrl, models] of Object.entries(value)) {
|
|
@@ -2909,6 +3066,71 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
|
|
|
2909
3066
|
void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
|
|
2910
3067
|
return { clientIds: normalized };
|
|
2911
3068
|
});
|
|
3069
|
+
},
|
|
3070
|
+
// ========== Failure Tracking ==========
|
|
3071
|
+
setFailedProviders: (value) => {
|
|
3072
|
+
const normalized = value.map((url) => normalizeBaseUrl5(url));
|
|
3073
|
+
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
|
|
3074
|
+
set({ failedProviders: normalized });
|
|
3075
|
+
},
|
|
3076
|
+
addFailedProvider: (baseUrl) => {
|
|
3077
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3078
|
+
const current = get().failedProviders;
|
|
3079
|
+
if (!current.includes(normalized)) {
|
|
3080
|
+
const updated = [...current, normalized];
|
|
3081
|
+
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
3082
|
+
set({ failedProviders: updated });
|
|
3083
|
+
}
|
|
3084
|
+
},
|
|
3085
|
+
removeFailedProvider: (baseUrl) => {
|
|
3086
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3087
|
+
const current = get().failedProviders;
|
|
3088
|
+
const updated = current.filter((url) => url !== normalized);
|
|
3089
|
+
void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
|
|
3090
|
+
set({ failedProviders: updated });
|
|
3091
|
+
},
|
|
3092
|
+
setLastFailed: (value) => {
|
|
3093
|
+
const normalized = {};
|
|
3094
|
+
for (const [baseUrl, timestamp] of Object.entries(value)) {
|
|
3095
|
+
normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
|
|
3096
|
+
}
|
|
3097
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
|
|
3098
|
+
set({ lastFailed: normalized });
|
|
3099
|
+
},
|
|
3100
|
+
setLastFailedTimestamp: (baseUrl, timestamp) => {
|
|
3101
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3102
|
+
const current = get().lastFailed;
|
|
3103
|
+
const updated = { ...current, [normalized]: timestamp };
|
|
3104
|
+
void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
|
|
3105
|
+
set({ lastFailed: updated });
|
|
3106
|
+
},
|
|
3107
|
+
setProvidersOnCooldown: (value) => {
|
|
3108
|
+
const normalized = value.map((entry) => ({
|
|
3109
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3110
|
+
timestamp: entry.timestamp
|
|
3111
|
+
}));
|
|
3112
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
|
|
3113
|
+
set({ providersOnCooldown: normalized });
|
|
3114
|
+
},
|
|
3115
|
+
addProviderOnCooldown: (baseUrl, timestamp) => {
|
|
3116
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3117
|
+
const current = get().providersOnCooldown;
|
|
3118
|
+
if (!current.some((entry) => entry.baseUrl === normalized)) {
|
|
3119
|
+
const updated = [...current, { baseUrl: normalized, timestamp }];
|
|
3120
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
3121
|
+
set({ providersOnCooldown: updated });
|
|
3122
|
+
}
|
|
3123
|
+
},
|
|
3124
|
+
removeProviderFromCooldown: (baseUrl) => {
|
|
3125
|
+
const normalized = normalizeBaseUrl5(baseUrl);
|
|
3126
|
+
const current = get().providersOnCooldown;
|
|
3127
|
+
const updated = current.filter((entry) => entry.baseUrl !== normalized);
|
|
3128
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
|
|
3129
|
+
set({ providersOnCooldown: updated });
|
|
3130
|
+
},
|
|
3131
|
+
clearProvidersOnCooldown: () => {
|
|
3132
|
+
void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, []);
|
|
3133
|
+
set({ providersOnCooldown: [] });
|
|
2912
3134
|
}
|
|
2913
3135
|
}));
|
|
2914
3136
|
var hydrateStoreFromDriver = async (store, driver) => {
|
|
@@ -2927,7 +3149,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
2927
3149
|
rawRoutstr21Models,
|
|
2928
3150
|
rawLastRoutstr21ModelsUpdate,
|
|
2929
3151
|
rawCachedReceiveTokens,
|
|
2930
|
-
rawClientIds
|
|
3152
|
+
rawClientIds,
|
|
3153
|
+
rawFailedProviders,
|
|
3154
|
+
rawLastFailed,
|
|
3155
|
+
rawProvidersOnCooldown
|
|
2931
3156
|
] = await Promise.all([
|
|
2932
3157
|
driver.getItem(
|
|
2933
3158
|
SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
|
|
@@ -2958,7 +3183,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
2958
3183
|
null
|
|
2959
3184
|
),
|
|
2960
3185
|
driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
|
|
2961
|
-
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
|
|
3186
|
+
driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, []),
|
|
3187
|
+
driver.getItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, []),
|
|
3188
|
+
driver.getItem(SDK_STORAGE_KEYS.LAST_FAILED, {}),
|
|
3189
|
+
driver.getItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, [])
|
|
2962
3190
|
]);
|
|
2963
3191
|
const modelsFromAllProviders = Object.fromEntries(
|
|
2964
3192
|
Object.entries(rawModels).map(([baseUrl, models]) => [
|
|
@@ -3026,6 +3254,17 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3026
3254
|
createdAt: entry.createdAt ?? Date.now(),
|
|
3027
3255
|
lastUsed: entry.lastUsed ?? null
|
|
3028
3256
|
}));
|
|
3257
|
+
const failedProviders = rawFailedProviders.map((url) => normalizeBaseUrl5(url));
|
|
3258
|
+
const lastFailed = Object.fromEntries(
|
|
3259
|
+
Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
|
|
3260
|
+
normalizeBaseUrl5(baseUrl),
|
|
3261
|
+
timestamp
|
|
3262
|
+
])
|
|
3263
|
+
);
|
|
3264
|
+
const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
|
|
3265
|
+
baseUrl: normalizeBaseUrl5(entry.baseUrl),
|
|
3266
|
+
timestamp: entry.timestamp
|
|
3267
|
+
}));
|
|
3029
3268
|
store.setState({
|
|
3030
3269
|
modelsFromAllProviders,
|
|
3031
3270
|
lastUsedModel,
|
|
@@ -3041,7 +3280,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
|
|
|
3041
3280
|
routstr21Models,
|
|
3042
3281
|
lastRoutstr21ModelsUpdate,
|
|
3043
3282
|
cachedReceiveTokens,
|
|
3044
|
-
clientIds
|
|
3283
|
+
clientIds,
|
|
3284
|
+
failedProviders,
|
|
3285
|
+
lastFailed,
|
|
3286
|
+
providersOnCooldown
|
|
3045
3287
|
});
|
|
3046
3288
|
};
|
|
3047
3289
|
var createSdkStore = ({
|
|
@@ -3207,11 +3449,11 @@ var RoutstrClient = class {
|
|
|
3207
3449
|
this.balanceManager
|
|
3208
3450
|
);
|
|
3209
3451
|
this.streamProcessor = new StreamProcessor();
|
|
3210
|
-
this.providerManager = new ProviderManager(providerRegistry);
|
|
3211
3452
|
this.alertLevel = alertLevel;
|
|
3212
3453
|
this.mode = mode;
|
|
3213
3454
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
3214
3455
|
this.sdkStore = options.sdkStore;
|
|
3456
|
+
this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore);
|
|
3215
3457
|
}
|
|
3216
3458
|
cashuSpender;
|
|
3217
3459
|
balanceManager;
|
|
@@ -3674,19 +3916,19 @@ var RoutstrClient = class {
|
|
|
3674
3916
|
`[RoutstrClient] _handleErrorResponse: Attempting to receive/restore token for ${baseUrl}`
|
|
3675
3917
|
);
|
|
3676
3918
|
if (params.token.startsWith("cashu")) {
|
|
3677
|
-
const
|
|
3919
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
3678
3920
|
params.token
|
|
3679
3921
|
);
|
|
3680
|
-
if (
|
|
3922
|
+
if (receiveResult.success) {
|
|
3681
3923
|
this._log(
|
|
3682
3924
|
"DEBUG",
|
|
3683
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${
|
|
3925
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
3684
3926
|
);
|
|
3685
3927
|
tryNextProvider = true;
|
|
3686
3928
|
} else {
|
|
3687
3929
|
this._log(
|
|
3688
3930
|
"DEBUG",
|
|
3689
|
-
`[RoutstrClient] _handleErrorResponse: Failed to receive token.
|
|
3931
|
+
`[RoutstrClient] _handleErrorResponse: Failed to receive token: ${receiveResult.message}`
|
|
3690
3932
|
);
|
|
3691
3933
|
}
|
|
3692
3934
|
}
|
|
@@ -3696,23 +3938,18 @@ var RoutstrClient = class {
|
|
|
3696
3938
|
"DEBUG",
|
|
3697
3939
|
`[RoutstrClient] _handleErrorResponse: Attempting to receive xcashu refund token, preview=${xCashuRefundToken.substring(0, 20)}...`
|
|
3698
3940
|
);
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
"xcashu refund failed",
|
|
3712
|
-
requestId
|
|
3713
|
-
);
|
|
3714
|
-
} catch (error) {
|
|
3715
|
-
this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
|
|
3941
|
+
const receiveResult = await this.cashuSpender.receiveToken(xCashuRefundToken);
|
|
3942
|
+
if (receiveResult.success) {
|
|
3943
|
+
this._log(
|
|
3944
|
+
"DEBUG",
|
|
3945
|
+
`[RoutstrClient] _handleErrorResponse: xcashu refund received, amount=${receiveResult.amount}`
|
|
3946
|
+
);
|
|
3947
|
+
tryNextProvider = true;
|
|
3948
|
+
} else {
|
|
3949
|
+
this._log(
|
|
3950
|
+
"ERROR",
|
|
3951
|
+
`[xcashu] Failed to receive refund token: ${receiveResult.message}`
|
|
3952
|
+
);
|
|
3716
3953
|
throw new ProviderError(
|
|
3717
3954
|
baseUrl,
|
|
3718
3955
|
status,
|
|
@@ -3741,6 +3978,10 @@ var RoutstrClient = class {
|
|
|
3741
3978
|
const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
3742
3979
|
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
3743
3980
|
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
3981
|
+
this._log(
|
|
3982
|
+
"DEBUG",
|
|
3983
|
+
`The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
|
|
3984
|
+
);
|
|
3744
3985
|
} catch (e) {
|
|
3745
3986
|
this._log(
|
|
3746
3987
|
"WARN",
|
|
@@ -3870,6 +4111,20 @@ var RoutstrClient = class {
|
|
|
3870
4111
|
tryNextProvider = true;
|
|
3871
4112
|
}
|
|
3872
4113
|
}
|
|
4114
|
+
if (status === 401 && this.mode === "apikeys") {
|
|
4115
|
+
this._log(
|
|
4116
|
+
"DEBUG",
|
|
4117
|
+
`[RoutstrClient] _handleErrorResponse: Checking balance for ${baseUrl}, key preview=${token}`
|
|
4118
|
+
);
|
|
4119
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
4120
|
+
token,
|
|
4121
|
+
baseUrl
|
|
4122
|
+
);
|
|
4123
|
+
if (latestBalanceInfo.isInvalidApiKey) {
|
|
4124
|
+
this.storageAdapter.removeApiKey(baseUrl);
|
|
4125
|
+
tryNextProvider = true;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
3873
4128
|
if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
|
|
3874
4129
|
this._log(
|
|
3875
4130
|
"DEBUG",
|
|
@@ -3880,13 +4135,13 @@ var RoutstrClient = class {
|
|
|
3880
4135
|
"DEBUG",
|
|
3881
4136
|
`[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
|
|
3882
4137
|
);
|
|
3883
|
-
const
|
|
4138
|
+
const latestBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
3884
4139
|
token,
|
|
3885
4140
|
baseUrl
|
|
3886
4141
|
);
|
|
3887
4142
|
this._log(
|
|
3888
4143
|
"DEBUG",
|
|
3889
|
-
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${
|
|
4144
|
+
`[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
|
|
3890
4145
|
);
|
|
3891
4146
|
const refundResult = await this.balanceManager.refundApiKey({
|
|
3892
4147
|
mintUrl,
|
|
@@ -3898,7 +4153,7 @@ var RoutstrClient = class {
|
|
|
3898
4153
|
"DEBUG",
|
|
3899
4154
|
`[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
|
|
3900
4155
|
);
|
|
3901
|
-
if (!refundResult.success &&
|
|
4156
|
+
if (!refundResult.success && latestBalanceInfo.amount > 0) {
|
|
3902
4157
|
throw new ProviderError(
|
|
3903
4158
|
baseUrl,
|
|
3904
4159
|
status,
|
|
@@ -3987,14 +4242,15 @@ var RoutstrClient = class {
|
|
|
3987
4242
|
if (this.mode === "xcashu" && response) {
|
|
3988
4243
|
const refundToken = response.headers.get("x-cashu") ?? void 0;
|
|
3989
4244
|
if (refundToken) {
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
4245
|
+
const receiveResult = await this.cashuSpender.receiveToken(refundToken);
|
|
4246
|
+
if (receiveResult.success) {
|
|
4247
|
+
this.storageAdapter.removeXcashuToken(baseUrl, token);
|
|
4248
|
+
satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
|
|
4249
|
+
} else {
|
|
4250
|
+
this._log(
|
|
4251
|
+
"ERROR",
|
|
4252
|
+
`[xcashu] Failed to receive refund token: ${receiveResult.message}`
|
|
4253
|
+
);
|
|
3998
4254
|
}
|
|
3999
4255
|
}
|
|
4000
4256
|
} else if (this.mode === "apikeys") {
|
|
@@ -4038,7 +4294,6 @@ var RoutstrClient = class {
|
|
|
4038
4294
|
try {
|
|
4039
4295
|
const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
|
|
4040
4296
|
this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
|
|
4041
|
-
const results = await this.cashuSpender.refundProviders(mintUrl);
|
|
4042
4297
|
} catch (error) {
|
|
4043
4298
|
this._log("ERROR", "Failed to refund providers:", error);
|
|
4044
4299
|
}
|
|
@@ -4247,18 +4502,18 @@ var RoutstrClient = class {
|
|
|
4247
4502
|
this.storageAdapter.setApiKey(baseUrl, spendResult2.token);
|
|
4248
4503
|
} catch (error) {
|
|
4249
4504
|
if (error instanceof Error && error.message.includes("ApiKey already exists")) {
|
|
4250
|
-
const
|
|
4505
|
+
const receiveResult = await this.cashuSpender.receiveToken(
|
|
4251
4506
|
spendResult2.token
|
|
4252
4507
|
);
|
|
4253
|
-
if (
|
|
4508
|
+
if (receiveResult.success) {
|
|
4254
4509
|
this._log(
|
|
4255
4510
|
"DEBUG",
|
|
4256
|
-
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${
|
|
4511
|
+
`[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${receiveResult.amount}`
|
|
4257
4512
|
);
|
|
4258
4513
|
} else {
|
|
4259
4514
|
this._log(
|
|
4260
4515
|
"DEBUG",
|
|
4261
|
-
`[RoutstrClient] _handleErrorResponse: Token restore failed
|
|
4516
|
+
`[RoutstrClient] _handleErrorResponse: Token restore failed: ${receiveResult.message}`
|
|
4262
4517
|
);
|
|
4263
4518
|
}
|
|
4264
4519
|
this._log(
|