@routstr/sdk 0.2.5 → 0.2.6

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.
@@ -1,16 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var vanilla = require('zustand/vanilla');
4
- var cashuTs = require('@cashu/cashu-ts');
5
4
  var stream = require('stream');
6
5
 
7
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
- }) : x)(function(x) {
10
- if (typeof require !== "undefined") return require.apply(this, arguments);
11
- throw Error('Dynamic require of "' + x + '" is not supported');
12
- });
13
-
14
6
  // core/errors.ts
15
7
  var InsufficientBalanceError = class extends Error {
16
8
  constructor(required, available, maxMintBalance = 0, maxMintUrl = "", customMessage) {
@@ -154,13 +146,8 @@ var CashuSpender = class {
154
146
  normalizedMintBalances[url] = balanceInSats;
155
147
  totalMintBalance += balanceInSats;
156
148
  }
157
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
158
149
  const providerBalances = {};
159
150
  let totalProviderBalance = 0;
160
- for (const pending of pendingDistribution) {
161
- providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
162
- totalProviderBalance += pending.amount;
163
- }
164
151
  const apiKeys = this.storageAdapter.getAllApiKeys();
165
152
  for (const apiKey of apiKeys) {
166
153
  if (!providerBalances[apiKey.baseUrl]) {
@@ -381,27 +368,11 @@ var CashuSpender = class {
381
368
  };
382
369
  }
383
370
  }
384
- if (token && baseUrl) {
385
- try {
386
- this.storageAdapter.setToken(baseUrl, token);
387
- } catch (error) {
388
- if (error instanceof Error && error.message.includes("Token already exists")) {
389
- this._log(
390
- "DEBUG",
391
- `[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
392
- );
393
- const receiveResult = await this.receiveToken(token);
394
- if (receiveResult.success) {
395
- this._log(
396
- "DEBUG",
397
- `[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
398
- );
399
- }
400
- token = this.storageAdapter.getToken(baseUrl);
401
- } else {
402
- throw error;
403
- }
404
- }
371
+ if (token) {
372
+ this._log(
373
+ "DEBUG",
374
+ `[CashuSpender] _spendInternal: Successfully spent ${spentAmount}, returning token with balance=${spentAmount}`
375
+ );
405
376
  }
406
377
  this._logTransaction("spend", {
407
378
  amount: spentAmount,
@@ -422,19 +393,19 @@ var CashuSpender = class {
422
393
  };
423
394
  }
424
395
  /**
425
- * Try to reuse an existing token
396
+ * Try to reuse an existing API key
426
397
  */
427
398
  async _tryReuseToken(baseUrl, amount, mintUrl) {
428
- const storedToken = this.storageAdapter.getToken(baseUrl);
429
- if (!storedToken) return null;
430
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
431
- const balanceForBaseUrl = pendingDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
432
- this._log("DEBUG", "RESUINGDSR GSODGNSD", balanceForBaseUrl, amount);
399
+ const apiKeyEntry = this.storageAdapter.getApiKey(baseUrl);
400
+ if (!apiKeyEntry) return null;
401
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
402
+ const balanceForBaseUrl = apiKeyDistribution.find((b) => b.baseUrl === baseUrl)?.amount || 0;
403
+ this._log("DEBUG", "Reusing API key", balanceForBaseUrl, amount);
433
404
  if (balanceForBaseUrl > amount) {
434
405
  const units = this.walletAdapter.getMintUnits();
435
406
  const unit = units[mintUrl] || "sat";
436
407
  return {
437
- token: storedToken,
408
+ token: apiKeyEntry.key,
438
409
  status: "success",
439
410
  balance: balanceForBaseUrl,
440
411
  unit
@@ -445,7 +416,8 @@ var CashuSpender = class {
445
416
  const topUpResult = await this.balanceManager.topUp({
446
417
  mintUrl,
447
418
  baseUrl,
448
- amount: topUpAmount
419
+ amount: topUpAmount,
420
+ token: apiKeyEntry.key
449
421
  });
450
422
  this._log("DEBUG", "TOPUP ", topUpResult);
451
423
  if (topUpResult.success && topUpResult.toppedUpAmount) {
@@ -459,7 +431,7 @@ var CashuSpender = class {
459
431
  status: "success"
460
432
  });
461
433
  return {
462
- token: storedToken,
434
+ token: apiKeyEntry.key,
463
435
  status: "success",
464
436
  balance: newBalance,
465
437
  unit
@@ -467,84 +439,108 @@ var CashuSpender = class {
467
439
  }
468
440
  const providerBalance = await this._getProviderTokenBalance(
469
441
  baseUrl,
470
- storedToken
442
+ apiKeyEntry.key
471
443
  );
472
444
  this._log("DEBUG", providerBalance);
473
445
  if (providerBalance <= 0) {
474
- this.storageAdapter.removeToken(baseUrl);
446
+ this.storageAdapter.removeApiKey(baseUrl);
475
447
  }
476
448
  }
477
449
  return null;
478
450
  }
479
451
  /**
480
- * Refund specific providers without retrying spend
452
+ * Refund all xcashu tokens from storage and increment tryCounts on failure.
453
+ * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
454
+ * @param mintUrl - The mint URL for receiving tokens
455
+ * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
456
+ * @returns Results for each xcashu token refund attempt
481
457
  */
482
- async refundProviders(baseUrls, mintUrl, refundApiKeys = false, forceRefund) {
458
+ async refundXcashuTokens(mintUrl, excludeBaseUrls) {
483
459
  const results = [];
484
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
485
- const toRefund = pendingDistribution.filter(
486
- (p) => baseUrls.includes(p.baseUrl)
487
- );
488
- const refundResults = await Promise.allSettled(
489
- toRefund.map(async (pending) => {
490
- const token = this.storageAdapter.getToken(pending.baseUrl);
491
- this._log("DEBUG", token, this.balanceManager);
492
- if (!token || !this.balanceManager) {
493
- return { baseUrl: pending.baseUrl, success: false };
494
- }
495
- const tokenBalance = await this.balanceManager.getTokenBalance(
496
- token,
497
- pending.baseUrl
498
- );
499
- if (tokenBalance.reserved > 0) {
500
- return { baseUrl: pending.baseUrl, success: false };
501
- }
502
- const result = await this.balanceManager.refund({
503
- mintUrl,
504
- baseUrl: pending.baseUrl,
505
- token
506
- });
507
- this._log("DEBUG", result);
508
- if (result.success) {
509
- this.storageAdapter.removeToken(pending.baseUrl);
510
- }
511
- return { baseUrl: pending.baseUrl, success: result.success };
512
- })
513
- );
514
- results.push(
515
- ...refundResults.map(
516
- (r) => r.status === "fulfilled" ? r.value : { baseUrl: "", success: false }
517
- )
518
- );
519
- if (refundApiKeys) {
520
- const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
521
- const apiKeysToRefund = apiKeyDistribution.filter(
522
- (p) => baseUrls.includes(p.baseUrl)
523
- );
524
- for (const apiKeyEntry of apiKeysToRefund) {
525
- const apiKeyEntryFull = this.storageAdapter.getApiKey(
526
- apiKeyEntry.baseUrl
527
- );
528
- if (apiKeyEntryFull && this.balanceManager) {
529
- const refundResult = await this.balanceManager.refundApiKey({
530
- mintUrl,
531
- baseUrl: apiKeyEntry.baseUrl,
532
- apiKey: apiKeyEntryFull.key,
533
- forceRefund
534
- });
535
- if (refundResult.success) {
536
- this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, 0);
460
+ const xcashuTokens = this.storageAdapter.getXcashuTokens();
461
+ const excludedUrls = new Set(excludeBaseUrls || []);
462
+ for (const [baseUrl, tokens] of Object.entries(xcashuTokens)) {
463
+ if (excludedUrls.has(baseUrl)) continue;
464
+ for (const xcashuToken of tokens) {
465
+ try {
466
+ const receiveResult = await this.receiveToken(xcashuToken.token);
467
+ if (receiveResult.success) {
468
+ this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
469
+ results.push({
470
+ baseUrl,
471
+ token: xcashuToken.token,
472
+ success: true
473
+ });
474
+ this._log(
475
+ "DEBUG",
476
+ `[CashuSpender] refundXcashuTokens: Successfully refunded xcashu token for ${baseUrl}, amount=${receiveResult.amount}`
477
+ );
478
+ } else {
479
+ const currentTryCount = xcashuToken.tryCount ?? 0;
480
+ const newTryCount = currentTryCount + 1;
481
+ this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
482
+ results.push({
483
+ baseUrl,
484
+ token: xcashuToken.token,
485
+ success: false,
486
+ error: receiveResult.message ?? "Refund failed"
487
+ });
488
+ this._log(
489
+ "DEBUG",
490
+ `[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
491
+ );
537
492
  }
493
+ } catch (error) {
494
+ const currentTryCount = xcashuToken.tryCount ?? 0;
495
+ const newTryCount = currentTryCount + 1;
496
+ this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
497
+ const errorMessage = error instanceof Error ? error.message : String(error);
538
498
  results.push({
539
- baseUrl: apiKeyEntry.baseUrl,
540
- success: refundResult.success
499
+ baseUrl,
500
+ token: xcashuToken.token,
501
+ success: false,
502
+ error: errorMessage
541
503
  });
504
+ this._log(
505
+ "ERROR",
506
+ `[CashuSpender] refundXcashuTokens: Exception during refund for ${baseUrl}: ${errorMessage}, incremented tryCount to ${newTryCount}`
507
+ );
508
+ }
509
+ }
510
+ }
511
+ return results;
512
+ }
513
+ /**
514
+ * Refund specific providers without retrying spend
515
+ */
516
+ async refundProviders(mintUrl, forceRefund) {
517
+ const results = [];
518
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
519
+ for (const apiKeyEntry of apiKeyDistribution) {
520
+ const apiKeyEntryFull = this.storageAdapter.getApiKey(
521
+ apiKeyEntry.baseUrl
522
+ );
523
+ if (apiKeyEntryFull && this.balanceManager) {
524
+ const refundResult = await this.balanceManager.refundApiKey({
525
+ mintUrl,
526
+ baseUrl: apiKeyEntry.baseUrl,
527
+ apiKey: apiKeyEntryFull.key,
528
+ forceRefund
529
+ });
530
+ if (refundResult.success) {
531
+ this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
542
532
  } else {
543
- results.push({
544
- baseUrl: apiKeyEntry.baseUrl,
545
- success: false
546
- });
533
+ this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
547
534
  }
535
+ results.push({
536
+ baseUrl: apiKeyEntry.baseUrl,
537
+ success: refundResult.success
538
+ });
539
+ } else {
540
+ results.push({
541
+ baseUrl: apiKeyEntry.baseUrl,
542
+ success: false
543
+ });
548
544
  }
549
545
  }
550
546
  return results;
@@ -629,13 +625,8 @@ var BalanceManager = class {
629
625
  normalizedMintBalances[url] = balanceInSats;
630
626
  totalMintBalance += balanceInSats;
631
627
  }
632
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
633
628
  const providerBalances = {};
634
629
  let totalProviderBalance = 0;
635
- for (const pending of pendingDistribution) {
636
- providerBalances[pending.baseUrl] = (providerBalances[pending.baseUrl] || 0) + pending.amount;
637
- totalProviderBalance += pending.amount;
638
- }
639
630
  const apiKeys = this.storageAdapter.getAllApiKeys();
640
631
  for (const apiKey of apiKeys) {
641
632
  if (!providerBalances[apiKey.baseUrl]) {
@@ -650,57 +641,6 @@ var BalanceManager = class {
650
641
  mintBalances: normalizedMintBalances
651
642
  };
652
643
  }
653
- /**
654
- * Unified refund - handles both NIP-60 and legacy wallet refunds
655
- */
656
- async refund(options) {
657
- const { mintUrl, baseUrl, token: providedToken } = options;
658
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
659
- if (!storedToken) {
660
- console.log("[BalanceManager] No token to refund, returning early");
661
- return { success: true, message: "No API key to refund" };
662
- }
663
- let fetchResult;
664
- try {
665
- fetchResult = await this._fetchRefundToken(baseUrl, storedToken);
666
- if (!fetchResult.success) {
667
- return {
668
- success: false,
669
- message: fetchResult.error || "Refund failed",
670
- requestId: fetchResult.requestId
671
- };
672
- }
673
- if (!fetchResult.token) {
674
- return {
675
- success: false,
676
- message: "No token received from refund",
677
- requestId: fetchResult.requestId
678
- };
679
- }
680
- if (fetchResult.error === "No balance to refund") {
681
- console.log(
682
- "[BalanceManager] No balance to refund, removing stored token"
683
- );
684
- this.storageAdapter.removeToken(baseUrl);
685
- return { success: true, message: "No balance to refund" };
686
- }
687
- const receiveResult = await this.cashuSpender.receiveToken(
688
- fetchResult.token
689
- );
690
- const totalAmountMsat = receiveResult.unit === "msat" ? receiveResult.amount : receiveResult.amount * 1e3;
691
- if (!providedToken) {
692
- this.storageAdapter.removeToken(baseUrl);
693
- }
694
- return {
695
- success: receiveResult.success,
696
- refundedAmount: totalAmountMsat,
697
- requestId: fetchResult.requestId
698
- };
699
- } catch (error) {
700
- console.error("[BalanceManager] Refund error", error);
701
- return this._handleRefundError(error, mintUrl, fetchResult?.requestId);
702
- }
703
- }
704
644
  /**
705
645
  * Refund API key balance - convert remaining API key balance to cashu token
706
646
  * @param options - Refund options including forceRefund flag
@@ -836,8 +776,9 @@ var BalanceManager = class {
836
776
  if (!amount || amount <= 0) {
837
777
  return { success: false, message: "Invalid top up amount" };
838
778
  }
839
- const storedToken = providedToken || this.storageAdapter.getToken(baseUrl);
840
- if (!storedToken) {
779
+ const apiKeyEntry = providedToken ? null : this.storageAdapter.getApiKey(baseUrl);
780
+ const apiKey = providedToken || apiKeyEntry?.key;
781
+ if (!apiKey) {
841
782
  return { success: false, message: "No API key available for top up" };
842
783
  }
843
784
  let cashuToken = null;
@@ -857,7 +798,7 @@ var BalanceManager = class {
857
798
  cashuToken = tokenResult.token;
858
799
  const topUpResult = await this._postTopUp(
859
800
  baseUrl,
860
- storedToken,
801
+ apiKey,
861
802
  cashuToken
862
803
  );
863
804
  requestId = topUpResult.requestId;
@@ -1072,38 +1013,11 @@ var BalanceManager = class {
1072
1013
  return candidates;
1073
1014
  }
1074
1015
  async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
1075
- const pendingDistribution = this.storageAdapter.getCachedTokenDistribution();
1076
1016
  const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
1077
1017
  const forceRefund = retryCount >= 2;
1078
- const toRefund = pendingDistribution.filter(
1079
- (pending) => pending.baseUrl !== baseUrl
1080
- );
1081
1018
  const apiKeysToRefund = apiKeyDistribution.filter(
1082
1019
  (apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0
1083
1020
  );
1084
- const tokenRefundResults = await Promise.allSettled(
1085
- toRefund.map(async (pending) => {
1086
- const token = this.storageAdapter.getToken(pending.baseUrl);
1087
- if (!token) {
1088
- return { baseUrl: pending.baseUrl, success: false };
1089
- }
1090
- const tokenBalance = await this.getTokenBalance(token, pending.baseUrl);
1091
- if (tokenBalance.reserved > 0) {
1092
- return { baseUrl: pending.baseUrl, success: false };
1093
- }
1094
- const result = await this.refund({
1095
- mintUrl,
1096
- baseUrl: pending.baseUrl,
1097
- token
1098
- });
1099
- return { baseUrl: pending.baseUrl, success: result.success };
1100
- })
1101
- );
1102
- for (const result of tokenRefundResults) {
1103
- if (result.status === "fulfilled" && result.value.success) {
1104
- this.storageAdapter.removeToken(result.value.baseUrl);
1105
- }
1106
- }
1107
1021
  const apiKeyRefundResults = await Promise.allSettled(
1108
1022
  apiKeysToRefund.map(async (apiKeyEntry) => {
1109
1023
  const fullApiKeyEntry = this.storageAdapter.getApiKey(
@@ -1127,77 +1041,6 @@ var BalanceManager = class {
1127
1041
  }
1128
1042
  }
1129
1043
  }
1130
- /**
1131
- * Fetch refund token from provider API
1132
- */
1133
- async _fetchRefundToken(baseUrl, storedToken) {
1134
- if (!baseUrl) {
1135
- return {
1136
- success: false,
1137
- error: "No base URL configured"
1138
- };
1139
- }
1140
- const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
1141
- const url = `${normalizedBaseUrl}v1/wallet/refund`;
1142
- const controller = new AbortController();
1143
- const timeoutId = setTimeout(() => {
1144
- controller.abort();
1145
- }, 6e4);
1146
- try {
1147
- const response = await fetch(url, {
1148
- method: "POST",
1149
- headers: {
1150
- Authorization: `Bearer ${storedToken}`,
1151
- "Content-Type": "application/json"
1152
- },
1153
- signal: controller.signal
1154
- });
1155
- clearTimeout(timeoutId);
1156
- const requestId = response.headers.get("x-routstr-request-id") || void 0;
1157
- if (!response.ok) {
1158
- const errorData = await response.json().catch(() => ({}));
1159
- if (response.status === 400 && errorData?.detail === "No balance to refund") {
1160
- this.storageAdapter.removeToken(baseUrl);
1161
- return {
1162
- success: false,
1163
- requestId,
1164
- error: "No balance to refund"
1165
- };
1166
- }
1167
- return {
1168
- success: false,
1169
- requestId,
1170
- error: `Refund request failed with status ${response.status}: ${errorData?.detail || response.statusText}`
1171
- };
1172
- }
1173
- const data = await response.json();
1174
- console.log("refund rsule", data);
1175
- return {
1176
- success: true,
1177
- token: data.token,
1178
- requestId
1179
- };
1180
- } catch (error) {
1181
- clearTimeout(timeoutId);
1182
- console.error("[BalanceManager._fetchRefundToken] Fetch error", error);
1183
- if (error instanceof Error) {
1184
- if (error.name === "AbortError") {
1185
- return {
1186
- success: false,
1187
- error: "Request timed out after 1 minute"
1188
- };
1189
- }
1190
- return {
1191
- success: false,
1192
- error: error.message
1193
- };
1194
- }
1195
- return {
1196
- success: false,
1197
- error: "Unknown error occurred during refund request"
1198
- };
1199
- }
1200
- }
1201
1044
  /**
1202
1045
  * Post topup request to provider API
1203
1046
  */
@@ -2181,38 +2024,54 @@ var createMemoryDriver = (seed) => {
2181
2024
  var isBun = () => {
2182
2025
  return typeof process.versions.bun !== "undefined";
2183
2026
  };
2184
- var createDatabase = (dbPath) => {
2027
+ var cachedDbModule = null;
2028
+ var loadDatabase = async (dbPath) => {
2185
2029
  if (isBun()) {
2186
2030
  throw new Error(
2187
- "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
2031
+ "SQLite driver not supported in Bun. Use createBunSqliteDriver() instead."
2188
2032
  );
2189
2033
  }
2190
- let Database = null;
2191
2034
  try {
2192
- Database = __require("better-sqlite3");
2035
+ if (!cachedDbModule) {
2036
+ cachedDbModule = (await import('better-sqlite3')).default;
2037
+ }
2038
+ return new cachedDbModule(dbPath);
2193
2039
  } catch (error) {
2194
2040
  throw new Error(
2195
2041
  `better-sqlite3 is required for sqlite storage. Install it to use sqlite storage. (${error})`
2196
2042
  );
2197
2043
  }
2198
- return new Database(dbPath);
2199
2044
  };
2200
2045
  var createSqliteDriver = (options = {}) => {
2201
2046
  const dbPath = options.dbPath || "routstr.sqlite";
2202
2047
  const tableName = options.tableName || "sdk_storage";
2203
- const db = createDatabase(dbPath);
2204
- db.exec(
2205
- `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
2206
- );
2207
- const selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
2208
- const upsertStmt = db.prepare(
2209
- `INSERT INTO ${tableName} (key, value) VALUES (?, ?)
2048
+ let db;
2049
+ let selectStmt;
2050
+ let upsertStmt;
2051
+ let deleteStmt;
2052
+ const initDb = async () => {
2053
+ if (!db) {
2054
+ db = await loadDatabase(dbPath);
2055
+ db.exec(
2056
+ `CREATE TABLE IF NOT EXISTS ${tableName} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`
2057
+ );
2058
+ selectStmt = db.prepare(`SELECT value FROM ${tableName} WHERE key = ?`);
2059
+ upsertStmt = db.prepare(
2060
+ `INSERT INTO ${tableName} (key, value) VALUES (?, ?)
2210
2061
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
2211
- );
2212
- const deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2062
+ );
2063
+ deleteStmt = db.prepare(`DELETE FROM ${tableName} WHERE key = ?`);
2064
+ }
2065
+ };
2066
+ const ensureInit = async () => {
2067
+ if (!db) {
2068
+ await initDb();
2069
+ }
2070
+ };
2213
2071
  return {
2214
2072
  async getItem(key, defaultValue) {
2215
2073
  try {
2074
+ await ensureInit();
2216
2075
  const row = selectStmt.get(key);
2217
2076
  if (!row || typeof row.value !== "string") return defaultValue;
2218
2077
  try {
@@ -2230,6 +2089,7 @@ var createSqliteDriver = (options = {}) => {
2230
2089
  },
2231
2090
  async setItem(key, value) {
2232
2091
  try {
2092
+ await ensureInit();
2233
2093
  upsertStmt.run(key, JSON.stringify(value));
2234
2094
  } catch (error) {
2235
2095
  console.error(`SQLite setItem failed for key "${key}":`, error);
@@ -2237,6 +2097,7 @@ var createSqliteDriver = (options = {}) => {
2237
2097
  },
2238
2098
  async removeItem(key) {
2239
2099
  try {
2100
+ await ensureInit();
2240
2101
  deleteStmt.run(key);
2241
2102
  } catch (error) {
2242
2103
  console.error(`SQLite removeItem failed for key "${key}":`, error);
@@ -2255,9 +2116,9 @@ var SDK_STORAGE_KEYS = {
2255
2116
  INFO_FROM_ALL_PROVIDERS: "info_from_all_providers",
2256
2117
  LAST_MODELS_UPDATE: "lastModelsUpdate",
2257
2118
  LAST_BASE_URLS_UPDATE: "lastBaseUrlsUpdate",
2258
- LOCAL_CASHU_TOKENS: "local_cashu_tokens",
2259
2119
  API_KEYS: "api_keys",
2260
2120
  CHILD_KEYS: "child_keys",
2121
+ XCASHU_TOKENS: "xcashu_tokens",
2261
2122
  ROUTSTR21_MODELS: "routstr21Models",
2262
2123
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
2263
2124
  CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
@@ -2448,21 +2309,23 @@ var normalizeBaseUrl2 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUr
2448
2309
  var isBun2 = () => {
2449
2310
  return typeof process.versions.bun !== "undefined";
2450
2311
  };
2451
- var createDatabase2 = (dbPath) => {
2312
+ var cachedDbModule2 = null;
2313
+ var loadDatabase2 = async (dbPath) => {
2452
2314
  if (isBun2()) {
2453
2315
  throw new Error(
2454
2316
  "SQLite driver not supported in Bun. Use createMemoryDriver() instead."
2455
2317
  );
2456
2318
  }
2457
- let Database = null;
2458
2319
  try {
2459
- Database = __require("better-sqlite3");
2320
+ if (!cachedDbModule2) {
2321
+ cachedDbModule2 = (await import('better-sqlite3')).default;
2322
+ }
2323
+ return new cachedDbModule2(dbPath);
2460
2324
  } catch (error) {
2461
2325
  throw new Error(
2462
2326
  `better-sqlite3 is required for sqlite usage tracking. Install it to use sqlite storage. (${error})`
2463
2327
  );
2464
2328
  }
2465
- return new Database(dbPath);
2466
2329
  };
2467
2330
  var buildWhereClause = (options = {}) => {
2468
2331
  const clauses = [];
@@ -2499,38 +2362,49 @@ var buildWhereClause = (options = {}) => {
2499
2362
  var createSqliteUsageTrackingDriver = (options = {}) => {
2500
2363
  const dbPath = options.dbPath || "routstr.sqlite";
2501
2364
  const tableName = options.tableName || "usage_tracking";
2502
- const db = createDatabase2(dbPath);
2503
2365
  const legacyStorageDriver = options.legacyStorageDriver;
2504
- db.exec(`
2505
- CREATE TABLE IF NOT EXISTS ${tableName} (
2506
- id TEXT PRIMARY KEY,
2507
- timestamp INTEGER NOT NULL,
2508
- model_id TEXT NOT NULL,
2509
- base_url TEXT NOT NULL,
2510
- request_id TEXT NOT NULL,
2511
- cost REAL NOT NULL,
2512
- sats_cost REAL NOT NULL,
2513
- prompt_tokens INTEGER NOT NULL,
2514
- completion_tokens INTEGER NOT NULL,
2515
- total_tokens INTEGER NOT NULL,
2516
- client TEXT,
2517
- session_id TEXT,
2518
- tags TEXT
2519
- );
2520
- CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
2521
- CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
2522
- CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
2523
- CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
2524
- CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
2525
- `);
2526
- const insertStmt = db.prepare(`
2527
- INSERT OR REPLACE INTO ${tableName} (
2528
- id, timestamp, model_id, base_url, request_id,
2529
- cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
2530
- client, session_id, tags
2531
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2532
- `);
2366
+ let db;
2367
+ let insertStmt;
2533
2368
  let migrationComplete = false;
2369
+ const initDb = async () => {
2370
+ if (!db) {
2371
+ db = await loadDatabase2(dbPath);
2372
+ db.exec(`
2373
+ CREATE TABLE IF NOT EXISTS ${tableName} (
2374
+ id TEXT PRIMARY KEY,
2375
+ timestamp INTEGER NOT NULL,
2376
+ model_id TEXT NOT NULL,
2377
+ base_url TEXT NOT NULL,
2378
+ request_id TEXT NOT NULL,
2379
+ cost REAL NOT NULL,
2380
+ sats_cost REAL NOT NULL,
2381
+ prompt_tokens INTEGER NOT NULL,
2382
+ completion_tokens INTEGER NOT NULL,
2383
+ total_tokens INTEGER NOT NULL,
2384
+ client TEXT,
2385
+ session_id TEXT,
2386
+ tags TEXT
2387
+ );
2388
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp);
2389
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id);
2390
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url);
2391
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_session_id ON ${tableName}(session_id);
2392
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_client ON ${tableName}(client);
2393
+ `);
2394
+ insertStmt = db.prepare(`
2395
+ INSERT OR REPLACE INTO ${tableName} (
2396
+ id, timestamp, model_id, base_url, request_id,
2397
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
2398
+ client, session_id, tags
2399
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2400
+ `);
2401
+ }
2402
+ };
2403
+ const ensureInit = async () => {
2404
+ if (!db) {
2405
+ await initDb();
2406
+ }
2407
+ };
2534
2408
  const appendOne = (entry) => {
2535
2409
  insertStmt.run(
2536
2410
  entry.id,
@@ -2588,19 +2462,23 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
2588
2462
  });
2589
2463
  return {
2590
2464
  async migrate() {
2465
+ await ensureInit();
2591
2466
  await ensureMigrated();
2592
2467
  },
2593
2468
  async append(entry) {
2469
+ await ensureInit();
2594
2470
  await ensureMigrated();
2595
2471
  appendOne(entry);
2596
2472
  },
2597
2473
  async appendMany(entries) {
2474
+ await ensureInit();
2598
2475
  await ensureMigrated();
2599
2476
  for (const entry of entries) {
2600
2477
  appendOne(entry);
2601
2478
  }
2602
2479
  },
2603
2480
  async list(options2 = {}) {
2481
+ await ensureInit();
2604
2482
  await ensureMigrated();
2605
2483
  const { sql, params } = buildWhereClause(options2);
2606
2484
  const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
@@ -2613,6 +2491,7 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
2613
2491
  return rows.map(mapRow);
2614
2492
  },
2615
2493
  async count(options2 = {}) {
2494
+ await ensureInit();
2616
2495
  await ensureMigrated();
2617
2496
  const { sql, params } = buildWhereClause(options2);
2618
2497
  const stmt = db.prepare(`SELECT COUNT(*) as count FROM ${tableName} ${sql}`);
@@ -2620,20 +2499,197 @@ var createSqliteUsageTrackingDriver = (options = {}) => {
2620
2499
  return Number(row?.count ?? 0);
2621
2500
  },
2622
2501
  async deleteOlderThan(timestamp) {
2502
+ await ensureInit();
2623
2503
  await ensureMigrated();
2624
2504
  const stmt = db.prepare(`DELETE FROM ${tableName} WHERE timestamp < ?`);
2625
2505
  const result = stmt.run(timestamp);
2626
2506
  return result.changes;
2627
2507
  },
2628
2508
  async clear() {
2509
+ await ensureInit();
2629
2510
  await ensureMigrated();
2630
2511
  db.prepare(`DELETE FROM ${tableName}`).run();
2631
2512
  }
2632
2513
  };
2633
2514
  };
2634
2515
 
2635
- // storage/usageTracking/memory.ts
2516
+ // storage/usageTracking/bunSqlite.ts
2517
+ var MIGRATION_MARKER_KEY3 = "usage_tracking_migration_v1";
2636
2518
  var normalizeBaseUrl3 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2519
+ var buildWhereClause2 = (options = {}) => {
2520
+ const clauses = [];
2521
+ const params = [];
2522
+ if (typeof options.before === "number") {
2523
+ clauses.push("timestamp < ?");
2524
+ params.push(options.before);
2525
+ }
2526
+ if (typeof options.after === "number") {
2527
+ clauses.push("timestamp > ?");
2528
+ params.push(options.after);
2529
+ }
2530
+ if (options.modelId) {
2531
+ clauses.push("model_id = ?");
2532
+ params.push(options.modelId);
2533
+ }
2534
+ if (options.baseUrl) {
2535
+ clauses.push("base_url = ?");
2536
+ params.push(normalizeBaseUrl3(options.baseUrl));
2537
+ }
2538
+ if (options.sessionId) {
2539
+ clauses.push("session_id = ?");
2540
+ params.push(options.sessionId);
2541
+ }
2542
+ if (options.client) {
2543
+ clauses.push("client = ?");
2544
+ params.push(options.client);
2545
+ }
2546
+ return {
2547
+ sql: clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "",
2548
+ params
2549
+ };
2550
+ };
2551
+ var createBunSqliteUsageTrackingDriver = (options = {}) => {
2552
+ const dbPath = options.dbPath || "routstr.sqlite";
2553
+ const tableName = options.tableName || "usage_tracking";
2554
+ const legacyStorageDriver = options.legacyStorageDriver;
2555
+ const SQLiteDatabase = options.sqlite?.Database;
2556
+ let migrationPromise = null;
2557
+ if (!SQLiteDatabase) {
2558
+ throw new Error(
2559
+ "Bun SQLite Database constructor is required. Pass { sqlite: { Database } } when creating the driver."
2560
+ );
2561
+ }
2562
+ const db = new SQLiteDatabase(dbPath);
2563
+ db.run(`
2564
+ CREATE TABLE IF NOT EXISTS ${tableName} (
2565
+ id TEXT PRIMARY KEY,
2566
+ timestamp INTEGER NOT NULL,
2567
+ model_id TEXT NOT NULL,
2568
+ base_url TEXT NOT NULL,
2569
+ request_id TEXT NOT NULL,
2570
+ cost REAL NOT NULL,
2571
+ sats_cost REAL NOT NULL,
2572
+ prompt_tokens INTEGER NOT NULL,
2573
+ completion_tokens INTEGER NOT NULL,
2574
+ total_tokens INTEGER NOT NULL,
2575
+ client TEXT,
2576
+ session_id TEXT,
2577
+ tags TEXT
2578
+ )
2579
+ `);
2580
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp)`);
2581
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_model_id ON ${tableName}(model_id)`);
2582
+ db.run(`CREATE INDEX IF NOT EXISTS idx_${tableName}_base_url ON ${tableName}(base_url)`);
2583
+ const appendOne = (entry) => {
2584
+ db.query(`
2585
+ INSERT OR REPLACE INTO ${tableName} (
2586
+ id, timestamp, model_id, base_url, request_id,
2587
+ cost, sats_cost, prompt_tokens, completion_tokens, total_tokens,
2588
+ client, session_id, tags
2589
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2590
+ `).run(
2591
+ entry.id,
2592
+ entry.timestamp,
2593
+ entry.modelId,
2594
+ normalizeBaseUrl3(entry.baseUrl),
2595
+ entry.requestId,
2596
+ entry.cost,
2597
+ entry.satsCost,
2598
+ entry.promptTokens,
2599
+ entry.completionTokens,
2600
+ entry.totalTokens,
2601
+ entry.client ?? null,
2602
+ entry.sessionId ?? null,
2603
+ JSON.stringify(entry.tags ?? [])
2604
+ );
2605
+ };
2606
+ const mapRow = (row) => ({
2607
+ id: row.id,
2608
+ timestamp: row.timestamp,
2609
+ modelId: row.model_id,
2610
+ baseUrl: row.base_url,
2611
+ requestId: row.request_id,
2612
+ cost: row.cost,
2613
+ satsCost: row.sats_cost,
2614
+ promptTokens: row.prompt_tokens,
2615
+ completionTokens: row.completion_tokens,
2616
+ totalTokens: row.total_tokens,
2617
+ client: row.client ?? void 0,
2618
+ sessionId: row.session_id ?? void 0,
2619
+ tags: typeof row.tags === "string" ? JSON.parse(row.tags) : void 0
2620
+ });
2621
+ const ensureMigrated = async () => {
2622
+ if (!legacyStorageDriver) return;
2623
+ if (!migrationPromise) {
2624
+ migrationPromise = (async () => {
2625
+ const migrated = await legacyStorageDriver.getItem(
2626
+ MIGRATION_MARKER_KEY3,
2627
+ false
2628
+ );
2629
+ if (migrated) return;
2630
+ const legacyEntries = await legacyStorageDriver.getItem(
2631
+ SDK_STORAGE_KEYS.USAGE_TRACKING,
2632
+ []
2633
+ );
2634
+ if (legacyEntries.length > 0) {
2635
+ for (const entry of legacyEntries) {
2636
+ appendOne(entry);
2637
+ }
2638
+ await legacyStorageDriver.removeItem(SDK_STORAGE_KEYS.USAGE_TRACKING);
2639
+ }
2640
+ await legacyStorageDriver.setItem(MIGRATION_MARKER_KEY3, true);
2641
+ })();
2642
+ }
2643
+ await migrationPromise;
2644
+ };
2645
+ return {
2646
+ async migrate() {
2647
+ await ensureMigrated();
2648
+ },
2649
+ async append(entry) {
2650
+ await ensureMigrated();
2651
+ appendOne(entry);
2652
+ },
2653
+ async appendMany(entries) {
2654
+ await ensureMigrated();
2655
+ for (const entry of entries) {
2656
+ appendOne(entry);
2657
+ }
2658
+ },
2659
+ async list(options2 = {}) {
2660
+ await ensureMigrated();
2661
+ const { sql, params } = buildWhereClause2(options2);
2662
+ const limitSql = typeof options2.limit === "number" ? " LIMIT ?" : "";
2663
+ const query = `SELECT * FROM ${tableName} ${sql} ORDER BY timestamp DESC${limitSql}`;
2664
+ let rows;
2665
+ if (typeof options2.limit === "number") {
2666
+ rows = db.query(query).all(...params, options2.limit);
2667
+ } else {
2668
+ rows = db.query(query).all(...params);
2669
+ }
2670
+ return rows.map(mapRow);
2671
+ },
2672
+ async count(options2 = {}) {
2673
+ const { sql, params } = buildWhereClause2(options2);
2674
+ const query = `SELECT COUNT(*) as count FROM ${tableName} ${sql}`;
2675
+ const row = db.query(query).get(...params);
2676
+ return Number(row?.count ?? 0);
2677
+ },
2678
+ async deleteOlderThan(timestamp) {
2679
+ await ensureMigrated();
2680
+ const before = timestamp;
2681
+ const result = db.query(`DELETE FROM ${tableName} WHERE timestamp < ?`).run(before);
2682
+ return result.changes ?? 0;
2683
+ },
2684
+ async clear() {
2685
+ await ensureMigrated();
2686
+ db.query(`DELETE FROM ${tableName}`).run();
2687
+ }
2688
+ };
2689
+ };
2690
+
2691
+ // storage/usageTracking/memory.ts
2692
+ var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2637
2693
  var matchesFilters2 = (entry, options = {}) => {
2638
2694
  if (typeof options.before === "number" && entry.timestamp >= options.before) {
2639
2695
  return false;
@@ -2644,7 +2700,7 @@ var matchesFilters2 = (entry, options = {}) => {
2644
2700
  if (options.modelId && entry.modelId !== options.modelId) {
2645
2701
  return false;
2646
2702
  }
2647
- if (options.baseUrl && normalizeBaseUrl3(entry.baseUrl) !== normalizeBaseUrl3(options.baseUrl)) {
2703
+ if (options.baseUrl && normalizeBaseUrl4(entry.baseUrl) !== normalizeBaseUrl4(options.baseUrl)) {
2648
2704
  return false;
2649
2705
  }
2650
2706
  if (options.sessionId && entry.sessionId !== options.sessionId) {
@@ -2658,18 +2714,18 @@ var matchesFilters2 = (entry, options = {}) => {
2658
2714
  var createMemoryUsageTrackingDriver = (seed = []) => {
2659
2715
  const store = /* @__PURE__ */ new Map();
2660
2716
  for (const entry of seed) {
2661
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
2717
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
2662
2718
  }
2663
2719
  return {
2664
2720
  async migrate() {
2665
2721
  return;
2666
2722
  },
2667
2723
  async append(entry) {
2668
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
2724
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
2669
2725
  },
2670
2726
  async appendMany(entries) {
2671
2727
  for (const entry of entries) {
2672
- store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl3(entry.baseUrl) });
2728
+ store.set(entry.id, { ...entry, baseUrl: normalizeBaseUrl4(entry.baseUrl) });
2673
2729
  }
2674
2730
  },
2675
2731
  async list(options = {}) {
@@ -2697,20 +2753,7 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
2697
2753
  }
2698
2754
  };
2699
2755
  };
2700
- var normalizeBaseUrl4 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2701
- var getCashuTokenBalance = (token) => {
2702
- try {
2703
- const decoded = cashuTs.getDecodedToken(token);
2704
- const unitDivisor = decoded.unit === "msat" ? 1e3 : 1;
2705
- let sum = 0;
2706
- for (const proof of decoded.proofs) {
2707
- sum += proof.amount / unitDivisor;
2708
- }
2709
- return sum;
2710
- } catch {
2711
- return 0;
2712
- }
2713
- };
2756
+ var normalizeBaseUrl5 = (baseUrl) => baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
2714
2757
  var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2715
2758
  modelsFromAllProviders: {},
2716
2759
  lastUsedModel: null,
@@ -2720,9 +2763,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2720
2763
  mintsFromAllProviders: {},
2721
2764
  infoFromAllProviders: {},
2722
2765
  lastModelsUpdate: {},
2723
- cachedTokens: [],
2724
2766
  apiKeys: [],
2725
2767
  childKeys: [],
2768
+ xcashuTokens: {},
2726
2769
  routstr21Models: [],
2727
2770
  lastRoutstr21ModelsUpdate: null,
2728
2771
  cachedReceiveTokens: [],
@@ -2730,7 +2773,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2730
2773
  setModelsFromAllProviders: (value) => {
2731
2774
  const normalized = {};
2732
2775
  for (const [baseUrl, models] of Object.entries(value)) {
2733
- normalized[normalizeBaseUrl4(baseUrl)] = models;
2776
+ normalized[normalizeBaseUrl5(baseUrl)] = models;
2734
2777
  }
2735
2778
  void driver.setItem(
2736
2779
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -2743,7 +2786,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2743
2786
  set({ lastUsedModel: value });
2744
2787
  },
2745
2788
  setBaseUrlsList: (value) => {
2746
- const normalized = value.map((url) => normalizeBaseUrl4(url));
2789
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
2747
2790
  void driver.setItem(SDK_STORAGE_KEYS.BASE_URLS_LIST, normalized);
2748
2791
  set({ baseUrlsList: normalized });
2749
2792
  },
@@ -2752,14 +2795,14 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2752
2795
  set({ lastBaseUrlsUpdate: value });
2753
2796
  },
2754
2797
  setDisabledProviders: (value) => {
2755
- const normalized = value.map((url) => normalizeBaseUrl4(url));
2798
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
2756
2799
  void driver.setItem(SDK_STORAGE_KEYS.DISABLED_PROVIDERS, normalized);
2757
2800
  set({ disabledProviders: normalized });
2758
2801
  },
2759
2802
  setMintsFromAllProviders: (value) => {
2760
2803
  const normalized = {};
2761
2804
  for (const [baseUrl, mints] of Object.entries(value)) {
2762
- normalized[normalizeBaseUrl4(baseUrl)] = mints.map(
2805
+ normalized[normalizeBaseUrl5(baseUrl)] = mints.map(
2763
2806
  (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
2764
2807
  );
2765
2808
  }
@@ -2772,7 +2815,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2772
2815
  setInfoFromAllProviders: (value) => {
2773
2816
  const normalized = {};
2774
2817
  for (const [baseUrl, info] of Object.entries(value)) {
2775
- normalized[normalizeBaseUrl4(baseUrl)] = info;
2818
+ normalized[normalizeBaseUrl5(baseUrl)] = info;
2776
2819
  }
2777
2820
  void driver.setItem(SDK_STORAGE_KEYS.INFO_FROM_ALL_PROVIDERS, normalized);
2778
2821
  set({ infoFromAllProviders: normalized });
@@ -2780,30 +2823,17 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2780
2823
  setLastModelsUpdate: (value) => {
2781
2824
  const normalized = {};
2782
2825
  for (const [baseUrl, timestamp] of Object.entries(value)) {
2783
- normalized[normalizeBaseUrl4(baseUrl)] = timestamp;
2826
+ normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
2784
2827
  }
2785
2828
  void driver.setItem(SDK_STORAGE_KEYS.LAST_MODELS_UPDATE, normalized);
2786
2829
  set({ lastModelsUpdate: normalized });
2787
2830
  },
2788
- setCachedTokens: (value) => {
2789
- set((state) => {
2790
- const updates = typeof value === "function" ? value(state.cachedTokens) : value;
2791
- const normalized = updates.map((entry) => ({
2792
- ...entry,
2793
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
2794
- balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
2795
- lastUsed: entry.lastUsed ?? null
2796
- }));
2797
- void driver.setItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, normalized);
2798
- return { cachedTokens: normalized };
2799
- });
2800
- },
2801
2831
  setApiKeys: (value) => {
2802
2832
  set((state) => {
2803
2833
  const updates = typeof value === "function" ? value(state.apiKeys) : value;
2804
2834
  const normalized = updates.map((entry) => ({
2805
2835
  ...entry,
2806
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
2836
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
2807
2837
  balance: entry.balance ?? 0,
2808
2838
  lastUsed: entry.lastUsed ?? null
2809
2839
  }));
@@ -2815,7 +2845,7 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2815
2845
  set((state) => {
2816
2846
  const updates = typeof value === "function" ? value(state.childKeys) : value;
2817
2847
  const normalized = updates.map((entry) => ({
2818
- parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
2848
+ parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
2819
2849
  childKey: entry.childKey,
2820
2850
  balance: entry.balance ?? 0,
2821
2851
  balanceLimit: entry.balanceLimit,
@@ -2826,6 +2856,30 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2826
2856
  return { childKeys: normalized };
2827
2857
  });
2828
2858
  },
2859
+ setXcashuTokens: (value) => {
2860
+ const normalized = {};
2861
+ for (const [baseUrl, tokens] of Object.entries(value)) {
2862
+ normalized[normalizeBaseUrl5(baseUrl)] = tokens.map((entry) => ({
2863
+ ...entry,
2864
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
2865
+ createdAt: entry.createdAt ?? Date.now(),
2866
+ tryCount: entry.tryCount ?? 0
2867
+ }));
2868
+ }
2869
+ void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, normalized);
2870
+ set({ xcashuTokens: normalized });
2871
+ },
2872
+ updateXcashuTokenTryCount: (token, tryCount) => {
2873
+ const currentTokens = get().xcashuTokens;
2874
+ const updatedTokens = {};
2875
+ for (const [baseUrl, tokens] of Object.entries(currentTokens)) {
2876
+ updatedTokens[baseUrl] = tokens.map(
2877
+ (entry) => entry.token === token ? { ...entry, tryCount } : entry
2878
+ );
2879
+ }
2880
+ void driver.setItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, updatedTokens);
2881
+ set({ xcashuTokens: updatedTokens });
2882
+ },
2829
2883
  setRoutstr21Models: (value) => {
2830
2884
  void driver.setItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, value);
2831
2885
  set({ routstr21Models: value });
@@ -2867,9 +2921,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
2867
2921
  rawMints,
2868
2922
  rawInfo,
2869
2923
  rawLastModelsUpdate,
2870
- rawCachedTokens,
2871
2924
  rawApiKeys,
2872
2925
  rawChildKeys,
2926
+ rawXcashuTokens,
2873
2927
  rawRoutstr21Models,
2874
2928
  rawLastRoutstr21ModelsUpdate,
2875
2929
  rawCachedReceiveTokens,
@@ -2895,9 +2949,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
2895
2949
  SDK_STORAGE_KEYS.LAST_MODELS_UPDATE,
2896
2950
  {}
2897
2951
  ),
2898
- driver.getItem(SDK_STORAGE_KEYS.LOCAL_CASHU_TOKENS, []),
2899
2952
  driver.getItem(SDK_STORAGE_KEYS.API_KEYS, []),
2900
2953
  driver.getItem(SDK_STORAGE_KEYS.CHILD_KEYS, []),
2954
+ driver.getItem(SDK_STORAGE_KEYS.XCASHU_TOKENS, {}),
2901
2955
  driver.getItem(SDK_STORAGE_KEYS.ROUTSTR21_MODELS, []),
2902
2956
  driver.getItem(
2903
2957
  SDK_STORAGE_KEYS.LAST_ROUTSTR21_MODELS_UPDATE,
@@ -2908,52 +2962,57 @@ var hydrateStoreFromDriver = async (store, driver) => {
2908
2962
  ]);
2909
2963
  const modelsFromAllProviders = Object.fromEntries(
2910
2964
  Object.entries(rawModels).map(([baseUrl, models]) => [
2911
- normalizeBaseUrl4(baseUrl),
2965
+ normalizeBaseUrl5(baseUrl),
2912
2966
  models
2913
2967
  ])
2914
2968
  );
2915
- const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl4(url));
2969
+ const baseUrlsList = rawBaseUrls.map((url) => normalizeBaseUrl5(url));
2916
2970
  const disabledProviders = rawDisabledProviders.map(
2917
- (url) => normalizeBaseUrl4(url)
2971
+ (url) => normalizeBaseUrl5(url)
2918
2972
  );
2919
2973
  const mintsFromAllProviders = Object.fromEntries(
2920
2974
  Object.entries(rawMints).map(([baseUrl, mints]) => [
2921
- normalizeBaseUrl4(baseUrl),
2975
+ normalizeBaseUrl5(baseUrl),
2922
2976
  mints.map((mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint)
2923
2977
  ])
2924
2978
  );
2925
2979
  const infoFromAllProviders = Object.fromEntries(
2926
2980
  Object.entries(rawInfo).map(([baseUrl, info]) => [
2927
- normalizeBaseUrl4(baseUrl),
2981
+ normalizeBaseUrl5(baseUrl),
2928
2982
  info
2929
2983
  ])
2930
2984
  );
2931
2985
  const lastModelsUpdate = Object.fromEntries(
2932
2986
  Object.entries(rawLastModelsUpdate).map(([baseUrl, timestamp]) => [
2933
- normalizeBaseUrl4(baseUrl),
2987
+ normalizeBaseUrl5(baseUrl),
2934
2988
  timestamp
2935
2989
  ])
2936
2990
  );
2937
- const cachedTokens = rawCachedTokens.map((entry) => ({
2938
- ...entry,
2939
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
2940
- balance: typeof entry.balance === "number" ? entry.balance : getCashuTokenBalance(entry.token),
2941
- lastUsed: entry.lastUsed ?? null
2942
- }));
2943
2991
  const apiKeys = rawApiKeys.map((entry) => ({
2944
2992
  ...entry,
2945
- baseUrl: normalizeBaseUrl4(entry.baseUrl),
2993
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
2946
2994
  balance: entry.balance ?? 0,
2947
2995
  lastUsed: entry.lastUsed ?? null
2948
2996
  }));
2949
2997
  const childKeys = rawChildKeys.map((entry) => ({
2950
- parentBaseUrl: normalizeBaseUrl4(entry.parentBaseUrl),
2998
+ parentBaseUrl: normalizeBaseUrl5(entry.parentBaseUrl),
2951
2999
  childKey: entry.childKey,
2952
3000
  balance: entry.balance ?? 0,
2953
3001
  balanceLimit: entry.balanceLimit,
2954
3002
  validityDate: entry.validityDate,
2955
3003
  createdAt: entry.createdAt ?? Date.now()
2956
3004
  }));
3005
+ const xcashuTokens = Object.fromEntries(
3006
+ Object.entries(rawXcashuTokens).map(([baseUrl, tokens]) => [
3007
+ normalizeBaseUrl5(baseUrl),
3008
+ tokens.map((entry) => ({
3009
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3010
+ token: entry.token,
3011
+ createdAt: entry.createdAt ?? Date.now(),
3012
+ tryCount: entry.tryCount ?? 0
3013
+ }))
3014
+ ])
3015
+ );
2957
3016
  const routstr21Models = rawRoutstr21Models;
2958
3017
  const lastRoutstr21ModelsUpdate = rawLastRoutstr21ModelsUpdate;
2959
3018
  const cachedReceiveTokens = rawCachedReceiveTokens?.map((entry) => ({
@@ -2976,9 +3035,9 @@ var hydrateStoreFromDriver = async (store, driver) => {
2976
3035
  mintsFromAllProviders,
2977
3036
  infoFromAllProviders,
2978
3037
  lastModelsUpdate,
2979
- cachedTokens,
2980
3038
  apiKeys,
2981
3039
  childKeys,
3040
+ xcashuTokens,
2982
3041
  routstr21Models,
2983
3042
  lastRoutstr21ModelsUpdate,
2984
3043
  cachedReceiveTokens,
@@ -3049,7 +3108,7 @@ var getDefaultUsageTrackingDriver = () => {
3049
3108
  return defaultUsageTrackingDriver;
3050
3109
  }
3051
3110
  if (isBun3()) {
3052
- defaultUsageTrackingDriver = createMemoryUsageTrackingDriver();
3111
+ defaultUsageTrackingDriver = createBunSqliteUsageTrackingDriver();
3053
3112
  return defaultUsageTrackingDriver;
3054
3113
  }
3055
3114
  if (isNode()) {
@@ -3063,16 +3122,20 @@ var getDefaultUsageTrackingDriver = () => {
3063
3122
  };
3064
3123
  function createSSEParserTransform(onUsage, onResponseId) {
3065
3124
  let buffer = "";
3125
+ let usageCaptured = false;
3126
+ let responseIdCaptured = false;
3066
3127
  const maybeCaptureUsageFromJson = (jsonText) => {
3067
3128
  try {
3068
3129
  const data = JSON.parse(jsonText);
3069
3130
  const responseId = data.id;
3070
3131
  if (typeof responseId === "string" && responseId.trim().length > 0) {
3071
3132
  onResponseId?.(responseId.trim());
3133
+ responseIdCaptured = true;
3072
3134
  }
3073
3135
  const usage = extractUsageFromSSEJson(data);
3074
3136
  if (usage) {
3075
3137
  onUsage(usage);
3138
+ usageCaptured = true;
3076
3139
  }
3077
3140
  } catch {
3078
3141
  }
@@ -3128,7 +3191,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
3128
3191
  }
3129
3192
  var TOPUP_MARGIN = 1.2;
3130
3193
  var RoutstrClient = class {
3131
- constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu") {
3194
+ constructor(walletAdapter, storageAdapter, providerRegistry, alertLevel, mode = "xcashu", options = {}) {
3132
3195
  this.walletAdapter = walletAdapter;
3133
3196
  this.storageAdapter = storageAdapter;
3134
3197
  this.providerRegistry = providerRegistry;
@@ -3146,13 +3209,9 @@ var RoutstrClient = class {
3146
3209
  this.streamProcessor = new StreamProcessor();
3147
3210
  this.providerManager = new ProviderManager(providerRegistry);
3148
3211
  this.alertLevel = alertLevel;
3149
- if (mode === "lazyrefund") {
3150
- this.mode = "apikeys";
3151
- } else if (mode === "apikeys") {
3152
- this.mode = "lazyrefund";
3153
- } else {
3154
- this.mode = mode;
3155
- }
3212
+ this.mode = mode;
3213
+ this.usageTrackingDriver = options.usageTrackingDriver;
3214
+ this.sdkStore = options.sdkStore;
3156
3215
  }
3157
3216
  cashuSpender;
3158
3217
  balanceManager;
@@ -3161,6 +3220,8 @@ var RoutstrClient = class {
3161
3220
  alertLevel;
3162
3221
  mode;
3163
3222
  debugLevel = "WARN";
3223
+ usageTrackingDriver;
3224
+ sdkStore;
3164
3225
  /**
3165
3226
  * Get the current client mode
3166
3227
  */
@@ -3230,11 +3291,13 @@ var RoutstrClient = class {
3230
3291
  const satsSpent = await this._handlePostResponseBalanceUpdate({
3231
3292
  token: prepared.tokenUsed,
3232
3293
  baseUrl: prepared.baseUrlUsed,
3294
+ mintUrl: params.mintUrl,
3233
3295
  initialTokenBalance: prepared.tokenBalanceInSats,
3234
3296
  response: prepared.response,
3235
3297
  modelId: prepared.modelId,
3236
3298
  usage: prepared.capturedUsage,
3237
- requestId: prepared.capturedResponseId
3299
+ requestId: prepared.capturedResponseId,
3300
+ clientApiKey: prepared.clientApiKey
3238
3301
  });
3239
3302
  prepared.response.satsSpent = satsSpent;
3240
3303
  prepared.response.usage = prepared.capturedUsage;
@@ -3253,11 +3316,13 @@ var RoutstrClient = class {
3253
3316
  const satsSpent = await this._handlePostResponseBalanceUpdate({
3254
3317
  token: prepared.tokenUsed,
3255
3318
  baseUrl: prepared.baseUrlUsed,
3319
+ mintUrl: params.mintUrl,
3256
3320
  initialTokenBalance: prepared.tokenBalanceInSats,
3257
3321
  response: prepared.response,
3258
3322
  modelId: prepared.modelId,
3259
3323
  usage: prepared.capturedUsage,
3260
- requestId: prepared.capturedResponseId
3324
+ requestId: prepared.capturedResponseId,
3325
+ clientApiKey: prepared.clientApiKey
3261
3326
  });
3262
3327
  prepared.response.satsSpent = satsSpent;
3263
3328
  res.end();
@@ -3273,11 +3338,13 @@ var RoutstrClient = class {
3273
3338
  const satsSpent = await this._handlePostResponseBalanceUpdate({
3274
3339
  token: prepared.tokenUsed,
3275
3340
  baseUrl: prepared.baseUrlUsed,
3341
+ mintUrl: params.mintUrl,
3276
3342
  initialTokenBalance: prepared.tokenBalanceInSats,
3277
3343
  response: prepared.response,
3278
3344
  modelId: prepared.modelId,
3279
3345
  usage: prepared.capturedUsage,
3280
- requestId: prepared.capturedResponseId
3346
+ requestId: prepared.capturedResponseId,
3347
+ clientApiKey: prepared.clientApiKey
3281
3348
  });
3282
3349
  prepared.response.satsSpent = satsSpent;
3283
3350
  prepared.response.usage = prepared.capturedUsage;
@@ -3307,8 +3374,10 @@ var RoutstrClient = class {
3307
3374
  headers = {},
3308
3375
  baseUrl,
3309
3376
  mintUrl,
3310
- modelId
3377
+ modelId,
3378
+ clientApiKey: providedClientApiKey
3311
3379
  } = params;
3380
+ const clientApiKey = providedClientApiKey ?? this._extractClientApiKey(headers);
3312
3381
  await this._checkBalance();
3313
3382
  let requiredSats = 1;
3314
3383
  let selectedModel;
@@ -3330,7 +3399,6 @@ var RoutstrClient = class {
3330
3399
  amount: requiredSats,
3331
3400
  baseUrl
3332
3401
  });
3333
- this._log("DEBUG", token, baseUrl);
3334
3402
  let requestBody = body;
3335
3403
  if (body && typeof body === "object") {
3336
3404
  const bodyObj = body;
@@ -3338,7 +3406,7 @@ var RoutstrClient = class {
3338
3406
  requestBody = { ...bodyObj, stream: false };
3339
3407
  }
3340
3408
  }
3341
- const baseHeaders = this._buildBaseHeaders(headers);
3409
+ const baseHeaders = this._buildBaseHeaders();
3342
3410
  const requestHeaders = this._withAuthHeader(baseHeaders, token);
3343
3411
  const response = await this._makeRequest({
3344
3412
  path,
@@ -3390,9 +3458,21 @@ var RoutstrClient = class {
3390
3458
  tokenBalanceInSats,
3391
3459
  modelId,
3392
3460
  capturedUsage,
3393
- capturedResponseId
3461
+ capturedResponseId,
3462
+ clientApiKey
3394
3463
  };
3395
3464
  }
3465
+ /**
3466
+ * Extract clientApiKey from Authorization Bearer token if present
3467
+ */
3468
+ _extractClientApiKey(headers) {
3469
+ const authHeader = headers["Authorization"] || headers["authorization"];
3470
+ if (authHeader?.startsWith("Bearer ")) {
3471
+ const extractedKey = authHeader.slice(7);
3472
+ return extractedKey;
3473
+ }
3474
+ return void 0;
3475
+ }
3396
3476
  /**
3397
3477
  * Fetch AI response with streaming
3398
3478
  */
@@ -3496,6 +3576,7 @@ var RoutstrClient = class {
3496
3576
  let satsSpent = await this._handlePostResponseBalanceUpdate({
3497
3577
  token,
3498
3578
  baseUrl: baseUrlUsed,
3579
+ mintUrl,
3499
3580
  initialTokenBalance: tokenBalanceInSats,
3500
3581
  fallbackSatsSpent: isApikeysEstimate ? this._getEstimatedCosts(selectedModel, streamingResult) : void 0,
3501
3582
  response,
@@ -3534,7 +3615,6 @@ var RoutstrClient = class {
3534
3615
  try {
3535
3616
  const url = `${baseUrl.replace(/\/$/, "")}${path}`;
3536
3617
  if (this.mode === "xcashu") this._log("DEBUG", "HEADERS,", headers);
3537
- this._log("DEBUG", "HEADERS,", headers);
3538
3618
  const response = await fetch(url, {
3539
3619
  method,
3540
3620
  headers,
@@ -3603,8 +3683,6 @@ var RoutstrClient = class {
3603
3683
  `[RoutstrClient] _handleErrorResponse: Token restored successfully, amount=${tryReceiveTokenResult.amount}`
3604
3684
  );
3605
3685
  tryNextProvider = true;
3606
- if (this.mode === "lazyrefund")
3607
- this.storageAdapter.removeToken(baseUrl);
3608
3686
  } else {
3609
3687
  this._log(
3610
3688
  "DEBUG",
@@ -3652,22 +3730,15 @@ var RoutstrClient = class {
3652
3730
  );
3653
3731
  }
3654
3732
  }
3655
- if (status === 402 && !tryNextProvider && (this.mode === "apikeys" || this.mode === "lazyrefund")) {
3733
+ if (status === 402 && !tryNextProvider && this.mode === "apikeys") {
3656
3734
  this.storageAdapter.getApiKey(baseUrl);
3657
3735
  let topupAmount = params.requiredSats;
3658
3736
  try {
3659
- let currentBalance = 0;
3660
- if (this.mode === "apikeys") {
3661
- const currentBalanceInfo = await this.balanceManager.getTokenBalance(
3662
- params.token,
3663
- baseUrl
3664
- );
3665
- currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
3666
- } else if (this.mode === "lazyrefund") {
3667
- const distribution = this.storageAdapter.getCachedTokenDistribution();
3668
- const tokenEntry = distribution.find((t) => t.baseUrl === baseUrl);
3669
- currentBalance = tokenEntry?.amount ?? 0;
3670
- }
3737
+ const currentBalanceInfo = await this.balanceManager.getTokenBalance(
3738
+ params.token,
3739
+ baseUrl
3740
+ );
3741
+ const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
3671
3742
  const shortfall = Math.max(0, params.requiredSats - currentBalance);
3672
3743
  topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
3673
3744
  } catch (e) {
@@ -3804,34 +3875,7 @@ var RoutstrClient = class {
3804
3875
  "DEBUG",
3805
3876
  `[RoutstrClient] _handleErrorResponse: Status ${status} (auth/server error), attempting refund for ${baseUrl}, mode=${this.mode}`
3806
3877
  );
3807
- if (this.mode === "lazyrefund") {
3808
- try {
3809
- const refundResult = await this.balanceManager.refund({
3810
- mintUrl,
3811
- baseUrl,
3812
- token: params.token
3813
- });
3814
- this._log(
3815
- "DEBUG",
3816
- `[RoutstrClient] _handleErrorResponse: Lazyrefund result: success=${refundResult.success}`
3817
- );
3818
- if (refundResult.success) this.storageAdapter.removeToken(baseUrl);
3819
- else
3820
- throw new ProviderError(
3821
- baseUrl,
3822
- status,
3823
- "refund failed",
3824
- requestId
3825
- );
3826
- } catch (error) {
3827
- throw new ProviderError(
3828
- baseUrl,
3829
- status,
3830
- "Failed to refund token",
3831
- requestId
3832
- );
3833
- }
3834
- } else if (this.mode === "apikeys") {
3878
+ if (this.mode === "apikeys") {
3835
3879
  this._log(
3836
3880
  "DEBUG",
3837
3881
  `[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
@@ -3847,7 +3891,8 @@ var RoutstrClient = class {
3847
3891
  const refundResult = await this.balanceManager.refundApiKey({
3848
3892
  mintUrl,
3849
3893
  baseUrl,
3850
- apiKey: token
3894
+ apiKey: token,
3895
+ forceRefund: true
3851
3896
  });
3852
3897
  this._log(
3853
3898
  "DEBUG",
@@ -3929,12 +3974,14 @@ var RoutstrClient = class {
3929
3974
  const {
3930
3975
  token,
3931
3976
  baseUrl,
3977
+ mintUrl,
3932
3978
  initialTokenBalance,
3933
3979
  fallbackSatsSpent,
3934
3980
  response,
3935
3981
  modelId,
3936
3982
  usage,
3937
- requestId
3983
+ requestId,
3984
+ clientApiKey
3938
3985
  } = params;
3939
3986
  let satsSpent = initialTokenBalance;
3940
3987
  if (this.mode === "xcashu" && response) {
@@ -3942,19 +3989,14 @@ var RoutstrClient = class {
3942
3989
  if (refundToken) {
3943
3990
  try {
3944
3991
  const receiveResult = await this.cashuSpender.receiveToken(refundToken);
3945
- satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
3992
+ if (receiveResult.success) {
3993
+ this.storageAdapter.removeXcashuToken(baseUrl, token);
3994
+ satsSpent = initialTokenBalance - receiveResult.amount * (receiveResult.unit == "sat" ? 1 : 1e3);
3995
+ }
3946
3996
  } catch (error) {
3947
3997
  this._log("ERROR", "[xcashu] Failed to receive refund token:", error);
3948
3998
  }
3949
3999
  }
3950
- } else if (this.mode === "lazyrefund") {
3951
- const latestBalanceInfo = await this.balanceManager.getTokenBalance(
3952
- token,
3953
- baseUrl
3954
- );
3955
- const latestTokenBalance = latestBalanceInfo.unit === "msat" ? latestBalanceInfo.amount / 1e3 : latestBalanceInfo.amount;
3956
- this.storageAdapter.updateTokenBalance(baseUrl, latestTokenBalance);
3957
- satsSpent = initialTokenBalance - latestTokenBalance;
3958
4000
  } else if (this.mode === "apikeys") {
3959
4001
  try {
3960
4002
  const latestBalanceInfo = await this.balanceManager.getTokenBalance(
@@ -3989,8 +4031,18 @@ var RoutstrClient = class {
3989
4031
  modelId,
3990
4032
  satsSpent,
3991
4033
  usage,
3992
- requestId
4034
+ requestId,
4035
+ clientApiKey
3993
4036
  });
4037
+ (async () => {
4038
+ try {
4039
+ const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
4040
+ this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
4041
+ const results = await this.cashuSpender.refundProviders(mintUrl);
4042
+ } catch (error) {
4043
+ this._log("ERROR", "Failed to refund providers:", error);
4044
+ }
4045
+ })();
3994
4046
  return satsSpent;
3995
4047
  }
3996
4048
  async _trackResponseUsage(params) {
@@ -4001,7 +4053,8 @@ var RoutstrClient = class {
4001
4053
  modelId,
4002
4054
  satsSpent,
4003
4055
  usage: providedUsage,
4004
- requestId: providedRequestId
4056
+ requestId: providedRequestId,
4057
+ clientApiKey
4005
4058
  } = params;
4006
4059
  if (!response || !modelId) {
4007
4060
  return;
@@ -4028,13 +4081,14 @@ var RoutstrClient = class {
4028
4081
  return;
4029
4082
  }
4030
4083
  const finalRequestId = requestId || "unknown";
4031
- const store = await getDefaultSdkStore();
4084
+ const store = this.sdkStore ?? await getDefaultSdkStore();
4032
4085
  const state = store.getState();
4086
+ const matchKey = clientApiKey ?? token;
4033
4087
  const matchingClient = state.clientIds.find(
4034
- (client) => client.apiKey === token
4088
+ (client) => client.apiKey === matchKey
4035
4089
  );
4036
4090
  const entryId = finalRequestId === "unknown" ? `req-${Date.now()}-${modelId}` : finalRequestId;
4037
- const usageTracking = getDefaultUsageTrackingDriver();
4091
+ const usageTracking = this.usageTrackingDriver ?? getDefaultUsageTrackingDriver();
4038
4092
  const entry = {
4039
4093
  id: entryId,
4040
4094
  timestamp: Date.now(),
@@ -4109,11 +4163,11 @@ var RoutstrClient = class {
4109
4163
  return estimatedCosts;
4110
4164
  }
4111
4165
  /**
4112
- * Get pending cashu token amount
4166
+ * Get pending API key amount
4113
4167
  */
4114
4168
  _getPendingCashuTokenAmount() {
4115
- const distribution = this.storageAdapter.getCachedTokenDistribution();
4116
- return distribution.reduce((total, item) => total + item.amount, 0);
4169
+ const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
4170
+ return apiKeyDistribution.reduce((total, item) => total + item.amount, 0);
4117
4171
  }
4118
4172
  /**
4119
4173
  * Handle errors and notify callbacks
@@ -4260,8 +4314,8 @@ var RoutstrClient = class {
4260
4314
  const spendResult = await this.cashuSpender.spend({
4261
4315
  mintUrl,
4262
4316
  amount,
4263
- baseUrl: this.mode === "lazyrefund" ? baseUrl : "",
4264
- reuseToken: this.mode === "lazyrefund"
4317
+ baseUrl: "",
4318
+ reuseToken: false
4265
4319
  });
4266
4320
  if (!spendResult.token) {
4267
4321
  this._log(
@@ -4274,6 +4328,7 @@ var RoutstrClient = class {
4274
4328
  "DEBUG",
4275
4329
  `[RoutstrClient] _spendToken: Cashu token created, token preview: ${spendResult.token}, balance: ${spendResult.balance} ${spendResult.unit ?? "sat"}`
4276
4330
  );
4331
+ this.storageAdapter.addXcashuToken(baseUrl, spendResult.token);
4277
4332
  }
4278
4333
  return {
4279
4334
  token: spendResult.token,