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