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