@routstr/sdk 0.1.7 → 0.2.0
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.js +140 -44
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +140 -44
- package/dist/client/index.mjs.map +1 -1
- package/dist/discovery/index.d.mts +2 -1
- package/dist/discovery/index.d.ts +2 -1
- package/dist/discovery/index.js +9 -1
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +9 -1
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +344 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +344 -178
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.d.mts +52 -1
- package/dist/storage/index.d.ts +52 -1
- package/dist/storage/index.js +192 -131
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/index.mjs +192 -131
- package/dist/storage/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +7 -1
- package/dist/wallet/index.d.ts +7 -1
- package/dist/wallet/index.js +110 -41
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +110 -41
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/client/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// core/errors.ts
|
|
2
2
|
var InsufficientBalanceError = class extends Error {
|
|
3
|
-
constructor(required, available, maxMintBalance = 0, maxMintUrl = "") {
|
|
3
|
+
constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
|
|
4
4
|
super(
|
|
5
|
-
`Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
|
|
5
|
+
customMessage ?? `Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
|
|
6
6
|
);
|
|
7
7
|
this.required = required;
|
|
8
8
|
this.available = available;
|
|
@@ -127,6 +127,9 @@ var CashuSpender = class {
|
|
|
127
127
|
return result;
|
|
128
128
|
}
|
|
129
129
|
async _getBalanceState() {
|
|
130
|
+
if (this.balanceManager) {
|
|
131
|
+
return this.balanceManager.getBalanceState();
|
|
132
|
+
}
|
|
130
133
|
const mintBalances = await this.walletAdapter.getBalances();
|
|
131
134
|
const units = this.walletAdapter.getMintUnits();
|
|
132
135
|
let totalMintBalance = 0;
|
|
@@ -142,15 +145,16 @@ var CashuSpender = class {
|
|
|
142
145
|
const providerBalances = {};
|
|
143
146
|
let totalProviderBalance = 0;
|
|
144
147
|
for (const pending of pendingDistribution) {
|
|
145
|
-
providerBalances[pending.baseUrl] = pending.amount;
|
|
148
|
+
providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
|
|
146
149
|
totalProviderBalance += pending.amount;
|
|
147
150
|
}
|
|
148
151
|
const apiKeys = this.storageAdapter.getAllApiKeys();
|
|
149
152
|
for (const apiKey of apiKeys) {
|
|
150
153
|
if (!providerBalances[apiKey.baseUrl]) {
|
|
151
|
-
providerBalances[apiKey.baseUrl] =
|
|
152
|
-
totalProviderBalance += apiKey.balance;
|
|
154
|
+
providerBalances[apiKey.baseUrl] = 0;
|
|
153
155
|
}
|
|
156
|
+
providerBalances[apiKey.baseUrl] += apiKey.balance;
|
|
157
|
+
totalProviderBalance += apiKey.balance;
|
|
154
158
|
}
|
|
155
159
|
return {
|
|
156
160
|
totalBalance: totalMintBalance + totalProviderBalance,
|
|
@@ -299,25 +303,12 @@ var CashuSpender = class {
|
|
|
299
303
|
`[CashuSpender] _spendInternal: Could not reuse token, will create new token`
|
|
300
304
|
);
|
|
301
305
|
}
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
let totalBalance = 0;
|
|
305
|
-
for (const url in balances) {
|
|
306
|
-
const balance = balances[url];
|
|
307
|
-
const unit = units[url];
|
|
308
|
-
const balanceInSats = getBalanceInSats(balance, unit);
|
|
309
|
-
totalBalance += balanceInSats;
|
|
310
|
-
}
|
|
311
|
-
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
312
|
-
const totalPending = pendingDistribution.reduce(
|
|
313
|
-
(sum, item) => sum + item.amount,
|
|
314
|
-
0
|
|
315
|
-
);
|
|
306
|
+
const balanceState = await this._getBalanceState();
|
|
307
|
+
const totalAvailableBalance = balanceState.totalBalance;
|
|
316
308
|
this._log(
|
|
317
309
|
"DEBUG",
|
|
318
|
-
`[CashuSpender] _spendInternal:
|
|
310
|
+
`[CashuSpender] _spendInternal: totalAvailableBalance=${totalAvailableBalance}, adjustedAmount=${adjustedAmount}`
|
|
319
311
|
);
|
|
320
|
-
const totalAvailableBalance = totalBalance + totalPending;
|
|
321
312
|
if (totalAvailableBalance < adjustedAmount) {
|
|
322
313
|
this._log(
|
|
323
314
|
"ERROR",
|
|
@@ -325,8 +316,7 @@ var CashuSpender = class {
|
|
|
325
316
|
);
|
|
326
317
|
return this._createInsufficientBalanceError(
|
|
327
318
|
adjustedAmount,
|
|
328
|
-
|
|
329
|
-
units,
|
|
319
|
+
balanceState.mintBalances,
|
|
330
320
|
totalAvailableBalance
|
|
331
321
|
);
|
|
332
322
|
}
|
|
@@ -346,8 +336,7 @@ var CashuSpender = class {
|
|
|
346
336
|
if ((tokenResult.error || "").includes("Insufficient balance")) {
|
|
347
337
|
return this._createInsufficientBalanceError(
|
|
348
338
|
adjustedAmount,
|
|
349
|
-
|
|
350
|
-
units,
|
|
339
|
+
balanceState.mintBalances,
|
|
351
340
|
totalAvailableBalance
|
|
352
341
|
);
|
|
353
342
|
}
|
|
@@ -392,6 +381,7 @@ var CashuSpender = class {
|
|
|
392
381
|
"DEBUG",
|
|
393
382
|
`[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
|
|
394
383
|
);
|
|
384
|
+
const units = this.walletAdapter.getMintUnits();
|
|
395
385
|
return {
|
|
396
386
|
token,
|
|
397
387
|
status: "success",
|
|
@@ -529,13 +519,11 @@ var CashuSpender = class {
|
|
|
529
519
|
/**
|
|
530
520
|
* Create an insufficient balance error result
|
|
531
521
|
*/
|
|
532
|
-
_createInsufficientBalanceError(required,
|
|
522
|
+
_createInsufficientBalanceError(required, normalizedBalances, availableBalance) {
|
|
533
523
|
let maxBalance = 0;
|
|
534
524
|
let maxMintUrl = "";
|
|
535
|
-
for (const mintUrl in
|
|
536
|
-
const
|
|
537
|
-
const unit = units[mintUrl];
|
|
538
|
-
const balanceInSats = getBalanceInSats(balance, unit);
|
|
525
|
+
for (const mintUrl in normalizedBalances) {
|
|
526
|
+
const balanceInSats = normalizedBalances[mintUrl];
|
|
539
527
|
if (balanceInSats > maxBalance) {
|
|
540
528
|
maxBalance = balanceInSats;
|
|
541
529
|
maxMintUrl = mintUrl;
|
|
@@ -596,6 +584,39 @@ var BalanceManager = class {
|
|
|
596
584
|
}
|
|
597
585
|
}
|
|
598
586
|
cashuSpender;
|
|
587
|
+
async getBalanceState() {
|
|
588
|
+
const mintBalances = await this.walletAdapter.getBalances();
|
|
589
|
+
const units = this.walletAdapter.getMintUnits();
|
|
590
|
+
let totalMintBalance = 0;
|
|
591
|
+
const normalizedMintBalances = {};
|
|
592
|
+
for (const url in mintBalances) {
|
|
593
|
+
const balance = mintBalances[url];
|
|
594
|
+
const unit = units[url];
|
|
595
|
+
const balanceInSats = getBalanceInSats(balance, unit);
|
|
596
|
+
normalizedMintBalances[url] = balanceInSats;
|
|
597
|
+
totalMintBalance += balanceInSats;
|
|
598
|
+
}
|
|
599
|
+
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
600
|
+
const providerBalances = {};
|
|
601
|
+
let totalProviderBalance = 0;
|
|
602
|
+
for (const pending of pendingDistribution) {
|
|
603
|
+
providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
|
|
604
|
+
totalProviderBalance += pending.amount;
|
|
605
|
+
}
|
|
606
|
+
const apiKeys = this.storageAdapter.getAllApiKeys();
|
|
607
|
+
for (const apiKey of apiKeys) {
|
|
608
|
+
if (!providerBalances[apiKey.baseUrl]) {
|
|
609
|
+
providerBalances[apiKey.baseUrl] = 0;
|
|
610
|
+
}
|
|
611
|
+
providerBalances[apiKey.baseUrl] += apiKey.balance;
|
|
612
|
+
totalProviderBalance += apiKey.balance;
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
totalBalance: totalMintBalance + totalProviderBalance,
|
|
616
|
+
providerBalances,
|
|
617
|
+
mintBalances: normalizedMintBalances
|
|
618
|
+
};
|
|
619
|
+
}
|
|
599
620
|
/**
|
|
600
621
|
* Unified refund - handles both NIP-60 and legacy wallet refunds
|
|
601
622
|
*/
|
|
@@ -806,6 +827,10 @@ var BalanceManager = class {
|
|
|
806
827
|
requestId
|
|
807
828
|
};
|
|
808
829
|
} catch (error) {
|
|
830
|
+
console.log(
|
|
831
|
+
"DEBUG",
|
|
832
|
+
`[TopuPU] topup: Topup result for ${baseUrl}: error=${error}`
|
|
833
|
+
);
|
|
809
834
|
if (cashuToken) {
|
|
810
835
|
await this._recoverFailedTopUp(cashuToken);
|
|
811
836
|
}
|
|
@@ -825,23 +850,42 @@ var BalanceManager = class {
|
|
|
825
850
|
if (!adjustedAmount || isNaN(adjustedAmount)) {
|
|
826
851
|
return { success: false, error: "Invalid top up amount" };
|
|
827
852
|
}
|
|
853
|
+
const balanceState = await this.getBalanceState();
|
|
828
854
|
const balances = await this.walletAdapter.getBalances();
|
|
829
855
|
const units = this.walletAdapter.getMintUnits();
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (totalMintBalance < adjustedAmount && totalMintBalance +
|
|
856
|
+
const totalMintBalance = Object.values(balanceState.mintBalances).reduce(
|
|
857
|
+
(sum, value) => sum + value,
|
|
858
|
+
0
|
|
859
|
+
);
|
|
860
|
+
const targetProviderBalance = balanceState.providerBalances[baseUrl] || 0;
|
|
861
|
+
const refundableProviderBalance = Object.entries(
|
|
862
|
+
balanceState.providerBalances
|
|
863
|
+
).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
|
|
864
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 1) {
|
|
839
865
|
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl);
|
|
840
866
|
return this.createProviderToken({
|
|
841
867
|
...options,
|
|
842
868
|
retryCount: retryCount + 1
|
|
843
869
|
});
|
|
844
870
|
}
|
|
871
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount) {
|
|
872
|
+
const error = new InsufficientBalanceError(
|
|
873
|
+
adjustedAmount,
|
|
874
|
+
totalMintBalance + targetProviderBalance,
|
|
875
|
+
totalMintBalance,
|
|
876
|
+
Object.entries(balanceState.mintBalances).reduce(
|
|
877
|
+
(max, [url, balance]) => balance > max.balance ? { url, balance } : max,
|
|
878
|
+
{ url: "", balance: 0 }
|
|
879
|
+
).url
|
|
880
|
+
);
|
|
881
|
+
return { success: false, error: error.message };
|
|
882
|
+
}
|
|
883
|
+
if (targetProviderBalance >= adjustedAmount) {
|
|
884
|
+
return {
|
|
885
|
+
success: true,
|
|
886
|
+
amountSpent: 0
|
|
887
|
+
};
|
|
888
|
+
}
|
|
845
889
|
const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
|
|
846
890
|
let requiredAmount = adjustedAmount;
|
|
847
891
|
const supportedMintsOnly = providerMints.length > 0;
|
|
@@ -957,10 +1001,14 @@ var BalanceManager = class {
|
|
|
957
1001
|
}
|
|
958
1002
|
async _refundOtherProvidersForTopUp(baseUrl, mintUrl) {
|
|
959
1003
|
const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
|
|
1004
|
+
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
960
1005
|
const toRefund = pendingDistribution.filter(
|
|
961
1006
|
(pending) => pending.baseUrl !== baseUrl
|
|
962
1007
|
);
|
|
963
|
-
const
|
|
1008
|
+
const apiKeysToRefund = apiKeyDistribution.filter(
|
|
1009
|
+
(apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
|
|
1010
|
+
);
|
|
1011
|
+
const tokenRefundResults = await Promise.allSettled(
|
|
964
1012
|
toRefund.map(async (pending) => {
|
|
965
1013
|
const token = this.storageAdapter.getToken(pending.baseUrl);
|
|
966
1014
|
if (!token) {
|
|
@@ -978,11 +1026,32 @@ var BalanceManager = class {
|
|
|
978
1026
|
return { baseUrl: pending.baseUrl, success: result.success };
|
|
979
1027
|
})
|
|
980
1028
|
);
|
|
981
|
-
for (const result of
|
|
1029
|
+
for (const result of tokenRefundResults) {
|
|
982
1030
|
if (result.status === "fulfilled" && result.value.success) {
|
|
983
1031
|
this.storageAdapter.removeToken(result.value.baseUrl);
|
|
984
1032
|
}
|
|
985
1033
|
}
|
|
1034
|
+
const apiKeyRefundResults = await Promise.allSettled(
|
|
1035
|
+
apiKeysToRefund.map(async (apiKeyEntry) => {
|
|
1036
|
+
const fullApiKeyEntry = this.storageAdapter.getApiKey(
|
|
1037
|
+
apiKeyEntry.baseUrl
|
|
1038
|
+
);
|
|
1039
|
+
if (!fullApiKeyEntry) {
|
|
1040
|
+
return { baseUrl: apiKeyEntry.baseUrl, success: false };
|
|
1041
|
+
}
|
|
1042
|
+
const result = await this.refundApiKey({
|
|
1043
|
+
mintUrl,
|
|
1044
|
+
baseUrl: apiKeyEntry.baseUrl,
|
|
1045
|
+
apiKey: fullApiKeyEntry.key
|
|
1046
|
+
});
|
|
1047
|
+
return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
|
|
1048
|
+
})
|
|
1049
|
+
);
|
|
1050
|
+
for (const result of apiKeyRefundResults) {
|
|
1051
|
+
if (result.status === "fulfilled" && result.value.success) {
|
|
1052
|
+
this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
986
1055
|
}
|
|
987
1056
|
/**
|
|
988
1057
|
* Fetch refund token from provider API
|
|
@@ -2177,10 +2246,34 @@ var RoutstrClient = class {
|
|
|
2177
2246
|
}
|
|
2178
2247
|
}
|
|
2179
2248
|
if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
|
|
2249
|
+
this.storageAdapter.getApiKey(baseUrl);
|
|
2250
|
+
let topupAmount = params.requiredSats;
|
|
2251
|
+
try {
|
|
2252
|
+
let currentBalance = 0;
|
|
2253
|
+
if (this.mode === "apikeys") {
|
|
2254
|
+
const currentBalanceInfo = await this.balanceManager.getTokenBalance(
|
|
2255
|
+
params.token,
|
|
2256
|
+
baseUrl
|
|
2257
|
+
);
|
|
2258
|
+
currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
|
|
2259
|
+
} else if (this.mode === "lazyrefund") {
|
|
2260
|
+
const distribution = this.storageAdapter.getCachedTokenDistribution();
|
|
2261
|
+
const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
|
|
2262
|
+
currentBalance = tokenEntry?.amount ?? 0;
|
|
2263
|
+
}
|
|
2264
|
+
const shortfall = Math.max(0, params.requiredSats - currentBalance);
|
|
2265
|
+
topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
|
|
2266
|
+
} catch (e) {
|
|
2267
|
+
this._log(
|
|
2268
|
+
"WARN",
|
|
2269
|
+
"Could not get current token balance for topup calculation:",
|
|
2270
|
+
e
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2180
2273
|
const topupResult = await this.balanceManager.topUp({
|
|
2181
2274
|
mintUrl,
|
|
2182
2275
|
baseUrl,
|
|
2183
|
-
amount:
|
|
2276
|
+
amount: topupAmount * TOPUP_MARGIN,
|
|
2184
2277
|
token: params.token
|
|
2185
2278
|
});
|
|
2186
2279
|
this._log(
|
|
@@ -2198,7 +2291,7 @@ var RoutstrClient = class {
|
|
|
2198
2291
|
"DEBUG",
|
|
2199
2292
|
`[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
|
|
2200
2293
|
);
|
|
2201
|
-
throw new InsufficientBalanceError(required, available);
|
|
2294
|
+
throw new InsufficientBalanceError(required, available, 0, "", message);
|
|
2202
2295
|
} else {
|
|
2203
2296
|
this._log(
|
|
2204
2297
|
"DEBUG",
|
|
@@ -2261,7 +2354,10 @@ var RoutstrClient = class {
|
|
|
2261
2354
|
retryToken = latestBalanceInfo.apiKey;
|
|
2262
2355
|
}
|
|
2263
2356
|
if (latestTokenBalance >= 0) {
|
|
2264
|
-
this.storageAdapter.updateApiKeyBalance(
|
|
2357
|
+
this.storageAdapter.updateApiKeyBalance(
|
|
2358
|
+
baseUrl,
|
|
2359
|
+
latestTokenBalance
|
|
2360
|
+
);
|
|
2265
2361
|
}
|
|
2266
2362
|
}
|
|
2267
2363
|
} catch (error) {
|