@routstr/sdk 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +2 -2
  2. package/dist/client/index.d.mts +0 -411
  3. package/dist/client/index.d.ts +0 -411
  4. package/dist/client/index.js +0 -4819
  5. package/dist/client/index.js.map +0 -1
  6. package/dist/client/index.mjs +0 -4813
  7. package/dist/client/index.mjs.map +0 -1
  8. package/dist/discovery/index.d.mts +0 -196
  9. package/dist/discovery/index.d.ts +0 -196
  10. package/dist/discovery/index.js +0 -616
  11. package/dist/discovery/index.js.map +0 -1
  12. package/dist/discovery/index.mjs +0 -613
  13. package/dist/discovery/index.mjs.map +0 -1
  14. package/dist/index.d.mts +0 -190
  15. package/dist/index.d.ts +0 -190
  16. package/dist/index.js +0 -6114
  17. package/dist/index.js.map +0 -1
  18. package/dist/index.mjs +0 -6065
  19. package/dist/index.mjs.map +0 -1
  20. package/dist/interfaces-Bp0Ngmqv.d.mts +0 -176
  21. package/dist/interfaces-CIfd_phZ.d.ts +0 -112
  22. package/dist/interfaces-Cxi8R4TT.d.mts +0 -112
  23. package/dist/interfaces-D2FDCLyP.d.ts +0 -176
  24. package/dist/storage/index.d.mts +0 -87
  25. package/dist/storage/index.d.ts +0 -87
  26. package/dist/storage/index.js +0 -1734
  27. package/dist/storage/index.js.map +0 -1
  28. package/dist/storage/index.mjs +0 -1712
  29. package/dist/storage/index.mjs.map +0 -1
  30. package/dist/store-BD5zF9Hp.d.ts +0 -172
  31. package/dist/store-CBSyK2qg.d.mts +0 -172
  32. package/dist/types-DPQM6tIG.d.mts +0 -234
  33. package/dist/types-DPQM6tIG.d.ts +0 -234
  34. package/dist/wallet/index.d.mts +0 -245
  35. package/dist/wallet/index.d.ts +0 -245
  36. package/dist/wallet/index.js +0 -1329
  37. package/dist/wallet/index.js.map +0 -1
  38. package/dist/wallet/index.mjs +0 -1326
  39. package/dist/wallet/index.mjs.map +0 -1
@@ -1,1329 +0,0 @@
1
- 'use strict';
2
-
3
- var cashuTs = require('@cashu/cashu-ts');
4
-
5
- // core/types.ts
6
- function makeConsoleLogger(prefix) {
7
- const fmt = (args) => prefix ? [prefix, ...args] : args;
8
- return {
9
- log: (...args) => console.log(...fmt(args)),
10
- warn: (...args) => console.warn(...fmt(args)),
11
- error: (...args) => console.error(...fmt(args)),
12
- debug: (...args) => console.log(...fmt(args)),
13
- child: (p) => makeConsoleLogger(prefix ? `${prefix}:${p}` : p)
14
- };
15
- }
16
- var consoleLogger = makeConsoleLogger();
17
-
18
- // core/errors.ts
19
- var InsufficientBalanceError = class extends Error {
20
- constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
21
- super(
22
- customMessage ?? `Insufficient balance: need ${required} sats, have ${available} sats available. ` + (maxMintBalance > 0 ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}` : "")
23
- );
24
- this.required = required;
25
- this.available = available;
26
- this.maxMintBalance = maxMintBalance;
27
- this.maxMintUrl = maxMintUrl;
28
- this.name = "InsufficientBalanceError";
29
- }
30
- };
31
-
32
- // wallet/AuditLogger.ts
33
- var AuditLogger = class _AuditLogger {
34
- static instance = null;
35
- static getInstance() {
36
- if (!_AuditLogger.instance) {
37
- _AuditLogger.instance = new _AuditLogger();
38
- }
39
- return _AuditLogger.instance;
40
- }
41
- async log(entry) {
42
- const fullEntry = {
43
- ...entry,
44
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
45
- };
46
- const logLine = JSON.stringify(fullEntry) + "\n";
47
- if (typeof window === "undefined") {
48
- try {
49
- const fs = await import('fs');
50
- const path = await import('path');
51
- const logPath = path.join(process.cwd(), "audit.log");
52
- fs.appendFileSync(logPath, logLine);
53
- } catch (error) {
54
- console.error("[AuditLogger] Failed to write to file:", error);
55
- }
56
- } else {
57
- console.log("[AUDIT]", logLine.trim());
58
- }
59
- }
60
- async logBalanceSnapshot(action, amounts, options) {
61
- await this.log({
62
- action,
63
- totalBalance: amounts.totalBalance,
64
- providerBalances: amounts.providerBalances,
65
- mintBalances: amounts.mintBalances,
66
- amount: options?.amount,
67
- mintUrl: options?.mintUrl,
68
- baseUrl: options?.baseUrl,
69
- status: options?.status ?? "success",
70
- details: options?.details
71
- });
72
- }
73
- };
74
- var auditLogger = AuditLogger.getInstance();
75
-
76
- // wallet/tokenUtils.ts
77
- function isNetworkErrorMessage(message) {
78
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed") || message.includes("ERR_TLS_CERT_ALTNAME_INVALID") || message.includes("ERR_TLS_CERT_NOT_YET_VALID") || message.includes("ERR_TLS_CERT_EXPIRED") || message.includes("UNABLE_TO_VERIFY_LEAF_SIGNATURE") || message.includes("SELF_SIGNED_CERT_IN_CHAIN");
79
- }
80
- function getBalanceInSats(balance, unit) {
81
- return unit === "msat" ? balance / 1e3 : balance;
82
- }
83
- function selectMintWithBalance(balances, units, amount, excludeMints = []) {
84
- for (const mintUrl in balances) {
85
- if (excludeMints.includes(mintUrl)) {
86
- continue;
87
- }
88
- const balanceInSats = getBalanceInSats(balances[mintUrl], units[mintUrl]);
89
- if (balanceInSats >= amount) {
90
- return { selectedMintUrl: mintUrl, selectedMintBalance: balanceInSats };
91
- }
92
- }
93
- return { selectedMintUrl: null, selectedMintBalance: 0 };
94
- }
95
- var CashuSpender = class {
96
- constructor(walletAdapter, storageAdapter, _providerRegistry, balanceManager, logger) {
97
- this.walletAdapter = walletAdapter;
98
- this.storageAdapter = storageAdapter;
99
- this._providerRegistry = _providerRegistry;
100
- this.balanceManager = balanceManager;
101
- this.logger = (logger ?? consoleLogger).child("CashuSpender");
102
- }
103
- _isBusy = false;
104
- debugLevel = "WARN";
105
- logger;
106
- async receiveToken(token) {
107
- try {
108
- const result = await this.walletAdapter.receiveToken(token);
109
- return result;
110
- } catch (error) {
111
- const errorMessage = error instanceof Error ? error.message : String(error);
112
- if (errorMessage.includes("Failed to fetch mint")) {
113
- const cachedTokens = this.storageAdapter.getCachedReceiveTokens();
114
- const existingIndex = cachedTokens.findIndex((t) => t.token === token);
115
- if (existingIndex === -1) {
116
- const { amount: amount2, unit: unit2 } = this._decodeTokenAmount(token);
117
- this.storageAdapter.setCachedReceiveTokens([
118
- ...cachedTokens,
119
- {
120
- token,
121
- amount: amount2,
122
- unit: unit2,
123
- createdAt: Date.now()
124
- }
125
- ]);
126
- }
127
- }
128
- const { amount, unit } = this._decodeTokenAmount(token);
129
- return { success: false, amount, unit, message: errorMessage };
130
- }
131
- }
132
- _decodeTokenAmount(token) {
133
- try {
134
- const decoded = cashuTs.getDecodedToken(token);
135
- const amount = decoded.proofs.reduce(
136
- (acc, proof) => acc + proof.amount,
137
- 0
138
- );
139
- const unit = decoded.unit || "sat";
140
- return { amount, unit };
141
- } catch {
142
- return { amount: 0, unit: "sat" };
143
- }
144
- }
145
- async _getBalanceState() {
146
- if (this.balanceManager) {
147
- return this.balanceManager.getBalanceState();
148
- }
149
- const mintBalances = await this.walletAdapter.getBalances();
150
- const units = this.walletAdapter.getMintUnits();
151
- let totalMintBalance = 0;
152
- const normalizedMintBalances = {};
153
- for (const url in mintBalances) {
154
- const balance = mintBalances[url];
155
- const unit = units[url];
156
- const balanceInSats = getBalanceInSats(balance, unit);
157
- normalizedMintBalances[url] = balanceInSats;
158
- totalMintBalance += balanceInSats;
159
- }
160
- const providerBalances = {};
161
- let totalProviderBalance = 0;
162
- const apiKeys = this.storageAdapter.getAllApiKeys();
163
- for (const apiKey of apiKeys) {
164
- if (!providerBalances[apiKey.baseUrl]) {
165
- providerBalances[apiKey.baseUrl] = 0;
166
- }
167
- providerBalances[apiKey.baseUrl] += apiKey.balance;
168
- totalProviderBalance += apiKey.balance;
169
- }
170
- return {
171
- totalBalance: totalMintBalance + totalProviderBalance,
172
- providerBalances,
173
- mintBalances: normalizedMintBalances
174
- };
175
- }
176
- async _logTransaction(action, options) {
177
- const balanceState = await this._getBalanceState();
178
- await auditLogger.logBalanceSnapshot(action, balanceState, options);
179
- }
180
- /**
181
- * Check if the spender is currently in a critical operation
182
- */
183
- get isBusy() {
184
- return this._isBusy;
185
- }
186
- getDebugLevel() {
187
- return this.debugLevel;
188
- }
189
- setDebugLevel(level) {
190
- this.debugLevel = level;
191
- }
192
- _log(level, ...args) {
193
- const levelPriority = {
194
- DEBUG: 0,
195
- WARN: 1,
196
- ERROR: 2
197
- };
198
- if (levelPriority[level] >= levelPriority[this.debugLevel]) {
199
- switch (level) {
200
- case "DEBUG":
201
- this.logger.log(...args);
202
- break;
203
- case "WARN":
204
- this.logger.warn(...args);
205
- break;
206
- case "ERROR":
207
- this.logger.error(...args);
208
- break;
209
- }
210
- }
211
- }
212
- /**
213
- * Spend Cashu tokens with automatic mint selection and retry logic
214
- * Throws errors on failure instead of returning failed SpendResult
215
- */
216
- async spend(options) {
217
- const {
218
- mintUrl,
219
- amount,
220
- baseUrl,
221
- reuseToken = false,
222
- p2pkPubkey,
223
- excludeMints = [],
224
- retryCount = 0
225
- } = options;
226
- this._isBusy = true;
227
- try {
228
- const result = await this._spendInternal({
229
- mintUrl,
230
- amount,
231
- baseUrl,
232
- reuseToken,
233
- p2pkPubkey,
234
- excludeMints,
235
- retryCount
236
- });
237
- if (result.status === "failed" || !result.token) {
238
- const errorMsg = result.error || `Insufficient balance. Need ${amount} sats.`;
239
- if (this._isNetworkError(errorMsg)) {
240
- throw new Error(
241
- `Your mint ${mintUrl} is unreachable or is blocking your IP. Please try again later or switch mints.`
242
- );
243
- }
244
- if (result.errorDetails) {
245
- throw new InsufficientBalanceError(
246
- result.errorDetails.required,
247
- result.errorDetails.available,
248
- result.errorDetails.maxMintBalance,
249
- result.errorDetails.maxMintUrl
250
- );
251
- }
252
- throw new Error(errorMsg);
253
- }
254
- return result;
255
- } finally {
256
- this._isBusy = false;
257
- }
258
- }
259
- /**
260
- * Check if error message indicates a network error
261
- */
262
- _isNetworkError(message) {
263
- return isNetworkErrorMessage(message) || message.includes("Your mint") && message.includes("unreachable");
264
- }
265
- /**
266
- * Internal spending logic
267
- */
268
- async _spendInternal(options) {
269
- let {
270
- mintUrl,
271
- amount,
272
- baseUrl,
273
- reuseToken,
274
- p2pkPubkey,
275
- excludeMints,
276
- retryCount
277
- } = options;
278
- this._log(
279
- "DEBUG",
280
- `[CashuSpender] _spendInternal: amount=${amount}, mintUrl=${mintUrl}, baseUrl=${baseUrl}, reuseToken=${reuseToken}`
281
- );
282
- let adjustedAmount = Math.ceil(amount);
283
- if (!adjustedAmount || isNaN(adjustedAmount)) {
284
- this._log(
285
- "ERROR",
286
- `[CashuSpender] _spendInternal: Invalid amount: ${amount}`
287
- );
288
- return {
289
- token: null,
290
- status: "failed",
291
- balance: 0,
292
- error: "Please enter a valid amount"
293
- };
294
- }
295
- if (reuseToken && baseUrl) {
296
- this._log(
297
- "DEBUG",
298
- `[CashuSpender] _spendInternal: Attempting to reuse token for ${baseUrl}`
299
- );
300
- const existingResult = await this._tryReuseToken(
301
- baseUrl,
302
- adjustedAmount,
303
- mintUrl
304
- );
305
- if (existingResult) {
306
- this._log(
307
- "DEBUG",
308
- `[CashuSpender] _spendInternal: Successfully reused token, balance: ${existingResult.balance}`
309
- );
310
- return existingResult;
311
- }
312
- this._log(
313
- "DEBUG",
314
- `[CashuSpender] _spendInternal: Could not reuse token, will create new token`
315
- );
316
- }
317
- const balanceState = await this._getBalanceState();
318
- const totalAvailableBalance = balanceState.totalBalance;
319
- this._log(
320
- "DEBUG",
321
- `[CashuSpender] _spendInternal: totalAvailableBalance=${totalAvailableBalance}, adjustedAmount=${adjustedAmount}`
322
- );
323
- if (totalAvailableBalance < adjustedAmount) {
324
- this._log(
325
- "ERROR",
326
- `[CashuSpender] _spendInternal: Insufficient balance, have=${totalAvailableBalance}, need=${adjustedAmount}`
327
- );
328
- return this._createInsufficientBalanceError(
329
- adjustedAmount,
330
- balanceState.mintBalances,
331
- totalAvailableBalance
332
- );
333
- }
334
- let token = null;
335
- let selectedMintUrl;
336
- let spentAmount = adjustedAmount;
337
- if (this.balanceManager) {
338
- const tokenResult = await this.balanceManager.createProviderToken({
339
- mintUrl,
340
- baseUrl,
341
- amount: adjustedAmount,
342
- p2pkPubkey,
343
- excludeMints,
344
- retryCount
345
- });
346
- if (!tokenResult.success || !tokenResult.token) {
347
- if ((tokenResult.error || "").includes("Insufficient balance")) {
348
- return this._createInsufficientBalanceError(
349
- adjustedAmount,
350
- balanceState.mintBalances,
351
- totalAvailableBalance
352
- );
353
- }
354
- return {
355
- token: null,
356
- status: "failed",
357
- balance: 0,
358
- error: tokenResult.error || "Failed to create token"
359
- };
360
- }
361
- token = tokenResult.token;
362
- selectedMintUrl = tokenResult.selectedMintUrl;
363
- spentAmount = tokenResult.amountSpent || adjustedAmount;
364
- } else {
365
- try {
366
- token = await this.walletAdapter.sendToken(
367
- mintUrl,
368
- adjustedAmount,
369
- p2pkPubkey
370
- );
371
- selectedMintUrl = mintUrl;
372
- } catch (error) {
373
- const errorMsg = error instanceof Error ? error.message : String(error);
374
- return {
375
- token: null,
376
- status: "failed",
377
- balance: 0,
378
- error: `Error generating token: ${errorMsg}`
379
- };
380
- }
381
- }
382
- if (token) {
383
- this._log(
384
- "DEBUG",
385
- `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
386
- );
387
- }
388
- this._logTransaction("spend", {
389
- amount: spentAmount,
390
- mintUrl: selectedMintUrl || mintUrl,
391
- baseUrl,
392
- status: "success"
393
- });
394
- this._log(
395
- "DEBUG",
396
- `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
397
- );
398
- const units = this.walletAdapter.getMintUnits();
399
- return {
400
- token,
401
- status: "success",
402
- balance: spentAmount,
403
- unit: (selectedMintUrl ? units[selectedMintUrl] : units[mintUrl]) || "sat"
404
- };
405
- }
406
- /**
407
- * Try to reuse an existing API key
408
- */
409
- async _tryReuseToken(baseUrl, amount, mintUrl) {
410
- const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
411
- if (!apiKeyEntry) return null;
412
- const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
413
- const balanceForBaseUrl = apiKeyDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
414
- this._log("DEBUG", "Reusing API key", balanceForBaseUrl, amount);
415
- if (balanceForBaseUrl > amount) {
416
- const units = this.walletAdapter.getMintUnits();
417
- const unit = units[mintUrl] || "sat";
418
- return {
419
- token: apiKeyEntry.key,
420
- status: "success",
421
- balance: balanceForBaseUrl,
422
- unit
423
- };
424
- }
425
- if (this.balanceManager) {
426
- const topUpAmount = Math.ceil(amount * 1.2 - balanceForBaseUrl);
427
- const topUpResult = await this.balanceManager.topUp({
428
- mintUrl,
429
- baseUrl,
430
- amount: topUpAmount,
431
- token: apiKeyEntry.key
432
- });
433
- this._log("DEBUG", "TOPUP ", topUpResult);
434
- if (topUpResult.success && topUpResult.toppedUpAmount) {
435
- const newBalance = balanceForBaseUrl + topUpResult.toppedUpAmount;
436
- const units = this.walletAdapter.getMintUnits();
437
- const unit = units[mintUrl] || "sat";
438
- this._logTransaction("topup", {
439
- amount: topUpResult.toppedUpAmount,
440
- mintUrl,
441
- baseUrl,
442
- status: "success"
443
- });
444
- return {
445
- token: apiKeyEntry.key,
446
- status: "success",
447
- balance: newBalance,
448
- unit
449
- };
450
- }
451
- const providerBalance = await this._getProviderTokenBalance(
452
- baseUrl,
453
- apiKeyEntry.key
454
- );
455
- this._log("DEBUG", providerBalance);
456
- if (providerBalance <= 0) {
457
- this.storageAdapter.removeApiKey(baseUrl);
458
- }
459
- }
460
- return null;
461
- }
462
- /**
463
- * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
464
- * The xcashu token acts as an API key to claim the refund, and the response contains
465
- * the actual refunded Cashu token which is then received into the wallet.
466
- * @param mintUrl - The mint URL for receiving tokens
467
- * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
468
- * @returns Results for each xcashu token refund attempt
469
- */
470
- async refundXcashuTokens(mintUrl, excludeBaseUrls) {
471
- const results = [];
472
- const xcashuTokens = this.storageAdapter.getXcashuTokens();
473
- const excludedUrls = new Set(excludeBaseUrls || []);
474
- for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
475
- if (excludedUrls.has(baseUrl)) continue;
476
- for (const xcashuToken of tokens) {
477
- try {
478
- if (!this.balanceManager) {
479
- throw new Error("BalanceManager not available for xcashu refund");
480
- }
481
- const fetchResult = await this.balanceManager.fetchRefundToken(
482
- baseUrl,
483
- xcashuToken.token,
484
- true
485
- );
486
- if (!fetchResult.success || !fetchResult.token) {
487
- throw new Error(
488
- fetchResult.error || "Failed to fetch refund token from provider"
489
- );
490
- }
491
- const receiveResult = await this.receiveToken(fetchResult.token);
492
- if (receiveResult.success) {
493
- this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
494
- results.push({
495
- baseUrl,
496
- token: xcashuToken.token,
497
- success: true
498
- });
499
- this._log(
500
- "DEBUG",
501
- `[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
502
- );
503
- } else {
504
- const currentTryCount = xcashuToken.tryCount ?? 0;
505
- const newTryCount = currentTryCount + 1;
506
- this.storageAdapter.updateXcashuTokenTryCount(
507
- xcashuToken.token,
508
- newTryCount
509
- );
510
- results.push({
511
- baseUrl,
512
- token: xcashuToken.token,
513
- success: false,
514
- error: receiveResult.message ?? "Refund failed"
515
- });
516
- this._log(
517
- "DEBUG",
518
- `[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}: ${receiveResult.message}`
519
- );
520
- }
521
- } catch (error) {
522
- const currentTryCount = xcashuToken.tryCount ?? 0;
523
- const newTryCount = currentTryCount + 1;
524
- this.storageAdapter.updateXcashuTokenTryCount(
525
- xcashuToken.token,
526
- newTryCount
527
- );
528
- const errorMessage = error instanceof Error ? error.message : String(error);
529
- results.push({
530
- baseUrl,
531
- token: xcashuToken.token,
532
- success: false,
533
- error: errorMessage
534
- });
535
- this._log(
536
- "ERROR",
537
- `[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
538
- );
539
- }
540
- }
541
- }
542
- return results;
543
- }
544
- /**
545
- * Refund specific providers without retrying spend
546
- */
547
- async refundProviders(mintUrl, forceRefund) {
548
- const results = [];
549
- const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
550
- for (const apiKeyEntry of apiKeyDistribution) {
551
- const apiKeyEntryFull = this.storageAdapter.getApiKey(
552
- apiKeyEntry.baseUrl
553
- );
554
- if (apiKeyEntryFull && this.balanceManager) {
555
- try {
556
- const balanceResult = await this.balanceManager.getTokenBalance(
557
- apiKeyEntryFull.key,
558
- apiKeyEntry.baseUrl
559
- );
560
- if (balanceResult.isInvalidApiKey) {
561
- this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
562
- results.push({
563
- baseUrl: apiKeyEntry.baseUrl,
564
- success: true
565
- });
566
- continue;
567
- }
568
- if (balanceResult.amount >= 0) {
569
- const balanceSat = balanceResult.unit === "msat" ? Math.floor(balanceResult.amount / 1e3) : balanceResult.amount;
570
- this.storageAdapter.updateApiKeyBalance(
571
- apiKeyEntry.baseUrl,
572
- balanceSat
573
- );
574
- }
575
- } catch {
576
- }
577
- const refreshedEntry = this.storageAdapter.getApiKey(
578
- apiKeyEntry.baseUrl
579
- );
580
- if (!refreshedEntry) continue;
581
- const refundResult = await this.balanceManager.refundApiKey({
582
- mintUrl,
583
- baseUrl: apiKeyEntry.baseUrl,
584
- apiKey: refreshedEntry.key,
585
- forceRefund
586
- });
587
- if (refundResult.success) {
588
- this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
589
- } else {
590
- const currentEntry = this.storageAdapter.getApiKey(
591
- apiKeyEntry.baseUrl
592
- );
593
- if (currentEntry) {
594
- this.storageAdapter.updateApiKeyBalance(
595
- apiKeyEntry.baseUrl,
596
- currentEntry.balance
597
- );
598
- }
599
- }
600
- results.push({
601
- baseUrl: apiKeyEntry.baseUrl,
602
- success: refundResult.success
603
- });
604
- } else {
605
- results.push({
606
- baseUrl: apiKeyEntry.baseUrl,
607
- success: false
608
- });
609
- }
610
- }
611
- return results;
612
- }
613
- /**
614
- * Create an insufficient balance error result
615
- */
616
- _createInsufficientBalanceError(required, normalizedBalances, availableBalance) {
617
- let maxBalance = 0;
618
- let maxMintUrl = "";
619
- for (const mintUrl in normalizedBalances) {
620
- const balanceInSats = normalizedBalances[mintUrl];
621
- if (balanceInSats > maxBalance) {
622
- maxBalance = balanceInSats;
623
- maxMintUrl = mintUrl;
624
- }
625
- }
626
- const error = new InsufficientBalanceError(
627
- required,
628
- availableBalance ?? maxBalance,
629
- maxBalance,
630
- maxMintUrl
631
- );
632
- return {
633
- token: null,
634
- status: "failed",
635
- balance: 0,
636
- error: error.message,
637
- errorDetails: {
638
- required,
639
- available: availableBalance ?? maxBalance,
640
- maxMintBalance: maxBalance,
641
- maxMintUrl
642
- }
643
- };
644
- }
645
- async _getProviderTokenBalance(baseUrl, token) {
646
- try {
647
- const response = await fetch(`${baseUrl}v1/wallet/info`, {
648
- headers: {
649
- Authorization: `Bearer ${token}`
650
- }
651
- });
652
- if (response.ok) {
653
- const data = await response.json();
654
- return data.balance / 1e3;
655
- }
656
- } catch {
657
- return 0;
658
- }
659
- return 0;
660
- }
661
- };
662
-
663
- // wallet/BalanceManager.ts
664
- var BalanceManager = class _BalanceManager {
665
- constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender, logger) {
666
- this.walletAdapter = walletAdapter;
667
- this.storageAdapter = storageAdapter;
668
- this.providerRegistry = providerRegistry;
669
- this.logger = (logger ?? consoleLogger).child("BalanceManager");
670
- if (cashuSpender) {
671
- this.cashuSpender = cashuSpender;
672
- } else {
673
- this.cashuSpender = new CashuSpender(
674
- walletAdapter,
675
- storageAdapter,
676
- providerRegistry,
677
- this
678
- );
679
- }
680
- }
681
- cashuSpender;
682
- /** In-memory guard for per-provider wallet mutations (topup / refund) */
683
- providerWalletOps = /* @__PURE__ */ new Map();
684
- /** Cooldown (ms) between opposite operations on the same provider */
685
- static PROVIDER_WALLET_COOLDOWN_MS = 1e4;
686
- logger;
687
- /**
688
- * Check whether a wallet operation (topup/refund) may run for a provider.
689
- * Returns the reason when blocked.
690
- */
691
- _canRunProviderWalletOperation(baseUrl, type) {
692
- const existing = this.providerWalletOps.get(baseUrl);
693
- if (!existing) {
694
- return { allowed: true };
695
- }
696
- if (existing.type === type) {
697
- return { allowed: true };
698
- }
699
- if (!existing.endTime) {
700
- return {
701
- allowed: false,
702
- reason: `Provider wallet operation locked; ${existing.type} in progress`
703
- };
704
- }
705
- const elapsed = Date.now() - existing.endTime;
706
- if (elapsed < _BalanceManager.PROVIDER_WALLET_COOLDOWN_MS) {
707
- return {
708
- allowed: false,
709
- reason: `Provider wallet operation locked; recent ${existing.type} completed ${Math.round(elapsed / 1e3)}s ago`
710
- };
711
- }
712
- this.providerWalletOps.delete(baseUrl);
713
- return { allowed: true };
714
- }
715
- _beginProviderWalletOperation(baseUrl, type) {
716
- this.providerWalletOps.set(baseUrl, { type, startTime: Date.now() });
717
- }
718
- _endProviderWalletOperation(baseUrl, type) {
719
- const existing = this.providerWalletOps.get(baseUrl);
720
- if (existing && existing.type === type) {
721
- existing.endTime = Date.now();
722
- }
723
- }
724
- async getBalanceState() {
725
- const mintBalances = await this.walletAdapter.getBalances();
726
- const units = this.walletAdapter.getMintUnits();
727
- let totalMintBalance = 0;
728
- const normalizedMintBalances = {};
729
- for (const url in mintBalances) {
730
- const balance = mintBalances[url];
731
- const unit = units[url];
732
- const balanceInSats = getBalanceInSats(balance, unit);
733
- normalizedMintBalances[url] = balanceInSats;
734
- totalMintBalance += balanceInSats;
735
- }
736
- const providerBalances = {};
737
- let totalProviderBalance = 0;
738
- const apiKeys = this.storageAdapter.getAllApiKeys();
739
- for (const apiKey of apiKeys) {
740
- if (!providerBalances[apiKey.baseUrl]) {
741
- providerBalances[apiKey.baseUrl] = 0;
742
- }
743
- providerBalances[apiKey.baseUrl] += apiKey.balance;
744
- totalProviderBalance += apiKey.balance;
745
- }
746
- return {
747
- totalBalance: totalMintBalance + totalProviderBalance,
748
- providerBalances,
749
- mintBalances: normalizedMintBalances
750
- };
751
- }
752
- /**
753
- * Refund API key balance - convert remaining API key balance to cashu token
754
- * @param options - Refund options including forceRefund flag
755
- * @returns Refund result
756
- */
757
- async refundApiKey(options) {
758
- const { mintUrl, baseUrl, apiKey, forceRefund } = options;
759
- const guard = this._canRunProviderWalletOperation(baseUrl, "refund");
760
- if (!guard.allowed) {
761
- this.logger.log(`Skipping refund for ${baseUrl} - ${guard.reason}`);
762
- return { success: false, message: guard.reason };
763
- }
764
- this._beginProviderWalletOperation(baseUrl, "refund");
765
- try {
766
- return await this._refundApiKeyImpl({ mintUrl, baseUrl, apiKey, forceRefund });
767
- } finally {
768
- this._endProviderWalletOperation(baseUrl, "refund");
769
- }
770
- }
771
- async _refundApiKeyImpl(options) {
772
- const { mintUrl, baseUrl, apiKey, forceRefund } = options;
773
- if (!apiKey) {
774
- return { success: false, message: "No API key to refund" };
775
- }
776
- if (!forceRefund) {
777
- const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
778
- if (apiKeyEntry?.lastUsed) {
779
- const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3;
780
- if (apiKeyEntry.lastUsed > fiveMinutesAgo) {
781
- this.logger.log(`Skipping refund for ${baseUrl} - used ${Math.round((Date.now() - apiKeyEntry.lastUsed) / 1e3)}s ago`);
782
- return {
783
- success: false,
784
- message: "API key was used recently, skipping refund"
785
- };
786
- }
787
- }
788
- }
789
- let fetchResult;
790
- try {
791
- fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
792
- if (!fetchResult.success) {
793
- return {
794
- success: false,
795
- message: fetchResult.error || "API key refund failed",
796
- requestId: fetchResult.requestId
797
- };
798
- }
799
- if (!fetchResult.token) {
800
- return {
801
- success: false,
802
- message: "No token received from API key refund",
803
- requestId: fetchResult.requestId
804
- };
805
- }
806
- if (fetchResult.error === "No balance to refund") {
807
- this.storageAdapter.removeApiKey(baseUrl);
808
- return { success: true, message: "No balance to refund, key cleaned up" };
809
- }
810
- const receiveResult = await this.cashuSpender.receiveToken(
811
- fetchResult.token
812
- );
813
- const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
814
- if (receiveResult.success) {
815
- this.storageAdapter.removeApiKey(baseUrl);
816
- }
817
- return {
818
- success: receiveResult.success,
819
- refundedAmount: totalAmountMsat,
820
- message: receiveResult.message,
821
- requestId: fetchResult.requestId
822
- };
823
- } catch (error) {
824
- this.logger.error("API key refund error", error);
825
- return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
826
- }
827
- }
828
- /**
829
- * Fetch refund token from provider API using API key (or xcashu token) authentication
830
- */
831
- async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
832
- if (!baseUrl) {
833
- return {
834
- success: false,
835
- error: "No base URL configured"
836
- };
837
- }
838
- const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
839
- const url = `${normalizedBaseUrl}v1/wallet/refund`;
840
- const controller = new AbortController();
841
- const timeoutId = setTimeout(() => {
842
- controller.abort();
843
- }, 6e4);
844
- try {
845
- const headers = {
846
- "Content-Type": "application/json"
847
- };
848
- if (xCashu) {
849
- headers["X-Cashu"] = apiKeyOrToken;
850
- } else {
851
- headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
852
- }
853
- const response = await fetch(url, {
854
- method: "POST",
855
- headers,
856
- signal: controller.signal
857
- });
858
- clearTimeout(timeoutId);
859
- const requestId = response.headers.get("x-routstr-request-id") || void 0;
860
- if (!response.ok) {
861
- const errorData = await response.json().catch(() => ({}));
862
- return {
863
- success: false,
864
- requestId,
865
- error: `API key refund failed: ${errorData?.detail || response.statusText}`
866
- };
867
- }
868
- const data = await response.json();
869
- return {
870
- success: true,
871
- token: data.token,
872
- requestId
873
- };
874
- } catch (error) {
875
- clearTimeout(timeoutId);
876
- this.logger.error("fetchRefundToken fetch error", error);
877
- if (error instanceof Error) {
878
- if (error.name === "AbortError") {
879
- return {
880
- success: false,
881
- error: "Request timed out after 1 minute"
882
- };
883
- }
884
- return {
885
- success: false,
886
- error: error.message
887
- };
888
- }
889
- return {
890
- success: false,
891
- error: "Unknown error occurred during API key refund request"
892
- };
893
- }
894
- }
895
- /**
896
- * Top up API key balance with a cashu token
897
- */
898
- async topUp(options) {
899
- const { mintUrl, baseUrl, amount, token: providedToken } = options;
900
- const guard = this._canRunProviderWalletOperation(baseUrl, "topup");
901
- if (!guard.allowed) {
902
- this.logger.log(`Skipping topup for ${baseUrl} - ${guard.reason}`);
903
- return { success: false, message: guard.reason };
904
- }
905
- this._beginProviderWalletOperation(baseUrl, "topup");
906
- try {
907
- return await this._topUpImpl({ mintUrl, baseUrl, amount, token: providedToken });
908
- } finally {
909
- this._endProviderWalletOperation(baseUrl, "topup");
910
- }
911
- }
912
- async _topUpImpl(options) {
913
- const { mintUrl, baseUrl, amount, token: providedToken } = options;
914
- if (!amount || amount <= 0) {
915
- return { success: false, message: "Invalid top up amount" };
916
- }
917
- const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
918
- const apiKey = providedToken || apiKeyEntry?.key;
919
- if (!apiKey) {
920
- return { success: false, message: "No API key available for top up" };
921
- }
922
- let cashuToken = null;
923
- let requestId;
924
- try {
925
- const tokenResult = await this.createProviderToken({
926
- mintUrl,
927
- baseUrl,
928
- amount
929
- });
930
- if (!tokenResult.success || !tokenResult.token) {
931
- return {
932
- success: false,
933
- message: tokenResult.error || "Unable to create top up token"
934
- };
935
- }
936
- cashuToken = tokenResult.token;
937
- const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
938
- requestId = topUpResult.requestId;
939
- this.logger.log("topUpResult:", topUpResult);
940
- if (!topUpResult.success) {
941
- await this._recoverFailedTopUp(cashuToken);
942
- return {
943
- success: false,
944
- message: topUpResult.error || "Top up failed",
945
- requestId,
946
- recoveredToken: true
947
- };
948
- }
949
- return {
950
- success: true,
951
- toppedUpAmount: amount,
952
- requestId
953
- };
954
- } catch (error) {
955
- this.logger.log(`topup error for ${baseUrl}: ${error}`);
956
- if (cashuToken) {
957
- await this._recoverFailedTopUp(cashuToken);
958
- }
959
- return this._handleTopUpError(error, mintUrl, requestId);
960
- }
961
- }
962
- async createProviderToken(options) {
963
- const {
964
- mintUrl,
965
- baseUrl,
966
- amount,
967
- retryCount = 0,
968
- excludeMints = [],
969
- p2pkPubkey
970
- } = options;
971
- const adjustedAmount = Math.ceil(amount);
972
- this.logger.log(`createProviderToken: baseUrl=${baseUrl} mintUrl=${mintUrl} amount=${amount} adjustedAmount=${adjustedAmount} retryCount=${retryCount}`);
973
- if (!adjustedAmount || isNaN(adjustedAmount)) {
974
- this.logger.error(`createProviderToken: invalid amount=${amount}`);
975
- return { success: false, error: "Invalid top up amount" };
976
- }
977
- const balanceState = await this.getBalanceState();
978
- const balances = await this.walletAdapter.getBalances();
979
- const units = this.walletAdapter.getMintUnits();
980
- const totalMintBalance = Object.values(balanceState.mintBalances).reduce(
981
- (sum, value) => sum + value,
982
- 0
983
- );
984
- const targetProviderBalance = balanceState.providerBalances[baseUrl] || 0;
985
- const refundableProviderBalance = Object.entries(
986
- balanceState.providerBalances
987
- ).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
988
- if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 2) {
989
- await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
990
- return this.createProviderToken({
991
- ...options,
992
- retryCount: retryCount + 1
993
- });
994
- }
995
- if (totalMintBalance + targetProviderBalance < adjustedAmount) {
996
- const error = new InsufficientBalanceError(
997
- adjustedAmount,
998
- totalMintBalance + targetProviderBalance,
999
- totalMintBalance,
1000
- Object.entries(balanceState.mintBalances).reduce(
1001
- (max, [url, balance]) => balance > max.balance ? { url, balance } : max,
1002
- { url: "", balance: 0 }
1003
- ).url
1004
- );
1005
- this.logger.error(`createProviderToken: insufficient balance required=${adjustedAmount} available=${totalMintBalance + targetProviderBalance} totalMint=${totalMintBalance} targetProvider=${targetProviderBalance}`);
1006
- return { success: false, error: error.message };
1007
- }
1008
- const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
1009
- let requiredAmount = adjustedAmount;
1010
- const supportedMintsOnly = providerMints.length > 0;
1011
- let candidates = this._selectCandidateMints({
1012
- balances,
1013
- units,
1014
- amount: requiredAmount,
1015
- preferredMintUrl: mintUrl,
1016
- excludeMints,
1017
- allowedMints: supportedMintsOnly ? providerMints : void 0
1018
- });
1019
- if (candidates.length === 0 && supportedMintsOnly) {
1020
- requiredAmount += 2;
1021
- candidates = this._selectCandidateMints({
1022
- balances,
1023
- units,
1024
- amount: requiredAmount,
1025
- preferredMintUrl: mintUrl,
1026
- excludeMints
1027
- });
1028
- }
1029
- if (candidates.length === 0) {
1030
- let maxBalance = 0;
1031
- let maxMintUrl = "";
1032
- for (const mintUrl2 in balances) {
1033
- const balance = balances[mintUrl2];
1034
- const unit = units[mintUrl2];
1035
- const balanceInSats = getBalanceInSats(balance, unit);
1036
- if (balanceInSats > maxBalance) {
1037
- maxBalance = balanceInSats;
1038
- maxMintUrl = mintUrl2;
1039
- }
1040
- }
1041
- this.logger.error(`createProviderToken: no candidate mints required=${requiredAmount} totalMint=${totalMintBalance} maxBalance=${maxBalance} maxMint=${maxMintUrl}`);
1042
- const error = new InsufficientBalanceError(
1043
- adjustedAmount,
1044
- totalMintBalance,
1045
- maxBalance,
1046
- maxMintUrl
1047
- );
1048
- return { success: false, error: error.message };
1049
- }
1050
- let lastError;
1051
- for (const candidateMint of candidates) {
1052
- try {
1053
- this.logger.log(`createProviderToken: attempting mint=${candidateMint} amount=${requiredAmount}`);
1054
- const token = await this.walletAdapter.sendToken(
1055
- candidateMint,
1056
- requiredAmount,
1057
- p2pkPubkey
1058
- );
1059
- this.logger.log(`createProviderToken: success from mint=${candidateMint}`);
1060
- return {
1061
- success: true,
1062
- token,
1063
- selectedMintUrl: candidateMint,
1064
- amountSpent: requiredAmount
1065
- };
1066
- } catch (error) {
1067
- const errorMsg = error instanceof Error ? error.message : String(error);
1068
- this.logger.error(`createProviderToken: mint=${candidateMint} failed: ${errorMsg}`);
1069
- if (error instanceof Error) {
1070
- lastError = errorMsg;
1071
- if (isNetworkErrorMessage(error.message)) {
1072
- this.logger.warn(`createProviderToken: network error from ${candidateMint}, trying next mint...`);
1073
- continue;
1074
- }
1075
- }
1076
- return {
1077
- success: false,
1078
- error: lastError || "Failed to create top up token"
1079
- };
1080
- }
1081
- }
1082
- this.logger.error(`createProviderToken: all candidate mints exhausted lastError=${lastError}`);
1083
- return {
1084
- success: false,
1085
- error: lastError || "All candidate mints failed while creating top up token"
1086
- };
1087
- }
1088
- _selectCandidateMints(options) {
1089
- const {
1090
- balances,
1091
- units,
1092
- amount,
1093
- preferredMintUrl,
1094
- excludeMints,
1095
- allowedMints
1096
- } = options;
1097
- const candidates = [];
1098
- const { selectedMintUrl: firstMint } = selectMintWithBalance(
1099
- balances,
1100
- units,
1101
- amount,
1102
- excludeMints
1103
- );
1104
- if (firstMint && (!allowedMints || allowedMints.length === 0 || allowedMints.includes(firstMint))) {
1105
- candidates.push(firstMint);
1106
- }
1107
- const canUseMint = (mint) => {
1108
- if (excludeMints.includes(mint)) return false;
1109
- if (allowedMints && allowedMints.length > 0 && !allowedMints.includes(mint)) {
1110
- return false;
1111
- }
1112
- const rawBalance = balances[mint] || 0;
1113
- const unit = units[mint];
1114
- const balanceInSats = getBalanceInSats(rawBalance, unit);
1115
- return balanceInSats >= amount;
1116
- };
1117
- if (preferredMintUrl && canUseMint(preferredMintUrl) && !candidates.includes(preferredMintUrl)) {
1118
- candidates.push(preferredMintUrl);
1119
- }
1120
- for (const mint in balances) {
1121
- if (mint === preferredMintUrl || candidates.includes(mint)) continue;
1122
- if (canUseMint(mint)) {
1123
- candidates.push(mint);
1124
- }
1125
- }
1126
- return candidates;
1127
- }
1128
- async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
1129
- const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1130
- const forceRefund = retryCount >= 2;
1131
- const apiKeysToRefund = apiKeyDistribution.filter(
1132
- (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1133
- );
1134
- const apiKeyRefundResults = await Promise.allSettled(
1135
- apiKeysToRefund.map(async (apiKeyEntry) => {
1136
- const fullApiKeyEntry = this.storageAdapter.getApiKey(
1137
- apiKeyEntry.baseUrl
1138
- );
1139
- if (!fullApiKeyEntry) {
1140
- return { baseUrl: apiKeyEntry.baseUrl, success: false };
1141
- }
1142
- const result = await this.refundApiKey({
1143
- mintUrl,
1144
- baseUrl: apiKeyEntry.baseUrl,
1145
- apiKey: fullApiKeyEntry.key,
1146
- forceRefund
1147
- });
1148
- return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
1149
- })
1150
- );
1151
- for (const result of apiKeyRefundResults) {
1152
- if (result.status === "fulfilled" && result.value.success) {
1153
- this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
1154
- }
1155
- }
1156
- }
1157
- /**
1158
- * Post topup request to provider API
1159
- */
1160
- async _postTopUp(baseUrl, storedToken, cashuToken) {
1161
- if (!baseUrl) {
1162
- return {
1163
- success: false,
1164
- error: "No base URL configured"
1165
- };
1166
- }
1167
- const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1168
- const url = `${normalizedBaseUrl}v1/wallet/topup?cashu_token=${encodeURIComponent(
1169
- cashuToken
1170
- )}`;
1171
- const controller = new AbortController();
1172
- const timeoutId = setTimeout(() => {
1173
- controller.abort();
1174
- }, 6e4);
1175
- try {
1176
- const response = await fetch(url, {
1177
- method: "POST",
1178
- headers: {
1179
- Authorization: `Bearer ${storedToken}`,
1180
- "Content-Type": "application/json"
1181
- },
1182
- signal: controller.signal
1183
- });
1184
- clearTimeout(timeoutId);
1185
- const requestId = response.headers.get("x-routstr-request-id") || void 0;
1186
- if (!response.ok) {
1187
- const errorData = await response.json().catch(() => ({}));
1188
- return {
1189
- success: false,
1190
- requestId,
1191
- error: errorData?.detail || `Top up failed with status ${response.status}`
1192
- };
1193
- }
1194
- return { success: true, requestId };
1195
- } catch (error) {
1196
- clearTimeout(timeoutId);
1197
- this.logger.error("_postTopUp fetch error", error);
1198
- if (error instanceof Error) {
1199
- if (error.name === "AbortError") {
1200
- return {
1201
- success: false,
1202
- error: "Request timed out after 1 minute"
1203
- };
1204
- }
1205
- return {
1206
- success: false,
1207
- error: error.message
1208
- };
1209
- }
1210
- return {
1211
- success: false,
1212
- error: "Unknown error occurred during top up request"
1213
- };
1214
- }
1215
- }
1216
- /**
1217
- * Attempt to receive token back after failed top up
1218
- */
1219
- async _recoverFailedTopUp(cashuToken) {
1220
- try {
1221
- await this.cashuSpender.receiveToken(cashuToken);
1222
- } catch (error) {
1223
- this.logger.error("_recoverFailedTopUp: failed to recover token", error);
1224
- }
1225
- }
1226
- /**
1227
- * Handle refund errors with specific error types
1228
- */
1229
- _handleRefundError(error, mintUrl, requestId) {
1230
- if (error instanceof Error) {
1231
- if (isNetworkErrorMessage(error.message)) {
1232
- return {
1233
- success: false,
1234
- message: `Failed to connect to the mint: ${mintUrl}`,
1235
- requestId
1236
- };
1237
- }
1238
- if (error.message.includes("Wallet not found")) {
1239
- return {
1240
- success: false,
1241
- message: `Wallet couldn't be loaded. Please save this refunded cashu token manually.`,
1242
- requestId
1243
- };
1244
- }
1245
- return {
1246
- success: false,
1247
- message: error.message,
1248
- requestId
1249
- };
1250
- }
1251
- return {
1252
- success: false,
1253
- message: "Refund failed",
1254
- requestId
1255
- };
1256
- }
1257
- /**
1258
- * Get token balance from provider
1259
- */
1260
- async getTokenBalance(token, baseUrl) {
1261
- try {
1262
- const response = await fetch(`${baseUrl}v1/wallet/info`, {
1263
- headers: {
1264
- Authorization: `Bearer ${token}`
1265
- }
1266
- });
1267
- if (response.ok) {
1268
- const data = await response.json();
1269
- return {
1270
- amount: data.balance,
1271
- reserved: data.reserved ?? 0,
1272
- unit: "msat",
1273
- apiKey: data.api_key
1274
- };
1275
- } else {
1276
- this.logger.warn(`getTokenBalance: status=${response.status}`);
1277
- const data = await response.json();
1278
- this.logger.warn("getTokenBalance: FAILED", data);
1279
- const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
1280
- return {
1281
- amount: -1,
1282
- reserved: data.reserved ?? 0,
1283
- unit: "msat",
1284
- apiKey: data.api_key,
1285
- isInvalidApiKey
1286
- };
1287
- }
1288
- } catch (error) {
1289
- this.logger.error("getTokenBalance error", error);
1290
- }
1291
- return { amount: -1, reserved: 0, unit: "sat", apiKey: "" };
1292
- }
1293
- /**
1294
- * Handle topup errors with specific error types
1295
- */
1296
- _handleTopUpError(error, mintUrl, requestId) {
1297
- if (error instanceof Error) {
1298
- if (isNetworkErrorMessage(error.message)) {
1299
- return {
1300
- success: false,
1301
- message: `Failed to connect to the mint: ${mintUrl}`,
1302
- requestId
1303
- };
1304
- }
1305
- if (error.message.includes("Wallet not found")) {
1306
- return {
1307
- success: false,
1308
- message: "Wallet couldn't be loaded. The cashu token was recovered locally.",
1309
- requestId
1310
- };
1311
- }
1312
- return {
1313
- success: false,
1314
- message: error.message,
1315
- requestId
1316
- };
1317
- }
1318
- return {
1319
- success: false,
1320
- message: "Top up failed",
1321
- requestId
1322
- };
1323
- }
1324
- };
1325
-
1326
- exports.BalanceManager = BalanceManager;
1327
- exports.CashuSpender = CashuSpender;
1328
- //# sourceMappingURL=index.js.map
1329
- //# sourceMappingURL=index.js.map