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