dexbot 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/credential-daemon.d.ts.map +1 -1
  2. package/dist/credential-daemon.js +6 -3
  3. package/dist/credential-daemon.js.map +1 -1
  4. package/dist/modules/bitshares-native/lru_cache.d.ts +9 -0
  5. package/dist/modules/bitshares-native/lru_cache.d.ts.map +1 -1
  6. package/dist/modules/bitshares-native/lru_cache.js +12 -0
  7. package/dist/modules/bitshares-native/lru_cache.js.map +1 -1
  8. package/dist/modules/bitshares-native/tx/builder.d.ts +4 -0
  9. package/dist/modules/bitshares-native/tx/builder.d.ts.map +1 -1
  10. package/dist/modules/bitshares-native/tx/builder.js +19 -0
  11. package/dist/modules/bitshares-native/tx/builder.js.map +1 -1
  12. package/dist/modules/bitshares-native/tx/tx_cache.d.ts +7 -0
  13. package/dist/modules/bitshares-native/tx/tx_cache.d.ts.map +1 -1
  14. package/dist/modules/bitshares-native/tx/tx_cache.js +11 -0
  15. package/dist/modules/bitshares-native/tx/tx_cache.js.map +1 -1
  16. package/dist/modules/bitshares_client.d.ts.map +1 -1
  17. package/dist/modules/bitshares_client.js +5 -0
  18. package/dist/modules/bitshares_client.js.map +1 -1
  19. package/dist/modules/constants.d.ts +1 -0
  20. package/dist/modules/constants.d.ts.map +1 -1
  21. package/dist/modules/constants.js +1 -0
  22. package/dist/modules/constants.js.map +1 -1
  23. package/dist/modules/credential_policy.d.ts.map +1 -1
  24. package/dist/modules/credential_policy.js +7 -5
  25. package/dist/modules/credential_policy.js.map +1 -1
  26. package/dist/modules/dexbot_class.d.ts +0 -1
  27. package/dist/modules/dexbot_class.d.ts.map +1 -1
  28. package/dist/modules/dexbot_class.js +119 -57
  29. package/dist/modules/dexbot_class.js.map +1 -1
  30. package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
  31. package/dist/modules/dexbot_fill_runtime.js +3 -0
  32. package/dist/modules/dexbot_fill_runtime.js.map +1 -1
  33. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  34. package/dist/modules/dexbot_maintenance_runtime.js +4 -0
  35. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  36. package/dist/modules/node_manager.d.ts +1 -0
  37. package/dist/modules/node_manager.d.ts.map +1 -1
  38. package/dist/modules/node_manager.js +11 -1
  39. package/dist/modules/node_manager.js.map +1 -1
  40. package/dist/modules/order/accounting.d.ts +8 -2
  41. package/dist/modules/order/accounting.d.ts.map +1 -1
  42. package/dist/modules/order/accounting.js +29 -16
  43. package/dist/modules/order/accounting.js.map +1 -1
  44. package/dist/modules/order/grid.js +1 -1
  45. package/dist/modules/order/grid.js.map +1 -1
  46. package/dist/modules/order/grid_reconcile_internal.js +13 -2
  47. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  48. package/dist/modules/order/index.d.ts +3 -8
  49. package/dist/modules/order/index.d.ts.map +1 -1
  50. package/dist/modules/order/index.js +3 -10
  51. package/dist/modules/order/index.js.map +1 -1
  52. package/dist/modules/order/manager.d.ts +84 -16
  53. package/dist/modules/order/manager.d.ts.map +1 -1
  54. package/dist/modules/order/manager.js +131 -235
  55. package/dist/modules/order/manager.js.map +1 -1
  56. package/dist/modules/order/strategy.js +1 -1
  57. package/dist/modules/order/strategy.js.map +1 -1
  58. package/dist/modules/order/sync_engine.d.ts +2 -3
  59. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  60. package/dist/modules/order/sync_engine.js +53 -20
  61. package/dist/modules/order/sync_engine.js.map +1 -1
  62. package/dist/modules/order/utils/math.d.ts +28 -1
  63. package/dist/modules/order/utils/math.d.ts.map +1 -1
  64. package/dist/modules/order/utils/math.js +40 -2
  65. package/dist/modules/order/utils/math.js.map +1 -1
  66. package/dist/modules/order/utils/order.d.ts.map +1 -1
  67. package/dist/modules/order/utils/order.js +42 -6
  68. package/dist/modules/order/utils/order.js.map +1 -1
  69. package/dist/modules/types.d.ts +7 -12
  70. package/dist/modules/types.d.ts.map +1 -1
  71. package/dist/modules/utils/math_utils.d.ts +3 -6
  72. package/dist/modules/utils/math_utils.d.ts.map +1 -1
  73. package/dist/modules/utils/math_utils.js +1 -14
  74. package/dist/modules/utils/math_utils.js.map +1 -1
  75. package/dist/{modules/order → scripts}/runner.d.ts +15 -9
  76. package/dist/scripts/runner.d.ts.map +1 -0
  77. package/dist/{modules/order → scripts}/runner.js +24 -45
  78. package/dist/scripts/runner.js.map +1 -0
  79. package/package.json +1 -2
  80. package/scripts/README.md +13 -0
  81. package/dist/modules/cli_whitelist_args.d.ts +0 -6
  82. package/dist/modules/cli_whitelist_args.d.ts.map +0 -1
  83. package/dist/modules/cli_whitelist_args.js +0 -13
  84. package/dist/modules/cli_whitelist_args.js.map +0 -1
  85. package/dist/modules/order/runner.d.ts.map +0 -1
  86. package/dist/modules/order/runner.js.map +0 -1
@@ -134,7 +134,6 @@ class DEXBot {
134
134
  _currentCycleId;
135
135
  _autoCancelOrphanCycleMarker;
136
136
  _autoCancelOrphanSubCount;
137
- _orphanFillsCreditedAt;
138
137
  _consecutiveConsumeFailures;
139
138
  _consumeFailureFirstAt;
140
139
  _reconnectUnregister;
@@ -226,7 +225,6 @@ class DEXBot {
226
225
  this._currentCycleId = 0;
227
226
  this._autoCancelOrphanCycleMarker = null;
228
227
  this._autoCancelOrphanSubCount = 0;
229
- this._orphanFillsCreditedAt = null;
230
228
  // Per-session guard: ghost order IDs successfully cancelled
231
229
  // (avoids spamming the chain with repeated cancel attempts for the same
232
230
  // orphan residual on every fill cycle).
@@ -349,7 +347,7 @@ class DEXBot {
349
347
  shadowLocks: this.manager?.shadowOrderIds?.size || 0,
350
348
  batchInFlight: this._batchInFlight,
351
349
  recoveryInFlight: this._recoverySyncInFlight,
352
- broadcasting: this.manager?._state?.isBroadcastingActive() || false
350
+ broadcasting: this.manager?.isBroadcastingActive?.() || false
353
351
  };
354
352
  }
355
353
  /**
@@ -1070,24 +1068,30 @@ class DEXBot {
1070
1068
  if (trackedFillKey && !this._isNewFillKey(trackedFillKey, processedFillKeys, '[POST-RESET]', fillOp.order_id)) {
1071
1069
  continue;
1072
1070
  }
1073
- const accountingResult = await this._applyReplaySafeTrackedFillAccounting(fill, fillOp, {
1074
- context: 'POST-RESET',
1075
- logger: { log: this._log.bind(this) },
1076
- replayMessage: (op) => `[POST-RESET] Replay detected for ${op.order_id}; skipping duplicate rebalance`
1077
- });
1078
- if (accountingResult.status === 'missing_key') {
1079
- requiresOpenOrdersSync = true;
1080
- continue;
1081
- }
1082
- if (accountingResult.status !== 'applied') {
1083
- continue;
1071
+ this.manager.lockOrders([gridOrder.id]);
1072
+ try {
1073
+ const accountingResult = await this._applyReplaySafeTrackedFillAccounting(fill, fillOp, {
1074
+ context: 'POST-RESET',
1075
+ logger: { log: this._log.bind(this) },
1076
+ replayMessage: (op) => `[POST-RESET] Replay detected for ${op.order_id}; skipping duplicate rebalance`
1077
+ });
1078
+ if (accountingResult.status === 'missing_key') {
1079
+ requiresOpenOrdersSync = true;
1080
+ continue;
1081
+ }
1082
+ if (accountingResult.status !== 'applied') {
1083
+ continue;
1084
+ }
1085
+ // Process this fill through the full rebalance pipeline
1086
+ // This will shift the boundary and place a new order on the filled slot
1087
+ const result = await this._processFillsWithBatching([gridOrder], new Set(), `[POST-RESET] fill ${gridOrder.id}`);
1088
+ if (result.aborted) {
1089
+ this._warn('[POST-RESET] Aborted batch due to illegal state; skipping grid persistence this cycle');
1090
+ continue;
1091
+ }
1084
1092
  }
1085
- // Process this fill through the full rebalance pipeline
1086
- // This will shift the boundary and place a new order on the filled slot
1087
- const result = await this._processFillsWithBatching([gridOrder], new Set(), `[POST-RESET] fill ${gridOrder.id}`);
1088
- if (result.aborted) {
1089
- this._warn('[POST-RESET] Aborted batch due to illegal state; skipping grid persistence this cycle');
1090
- continue;
1093
+ finally {
1094
+ this.manager.unlockOrders([gridOrder.id]);
1091
1095
  }
1092
1096
  }
1093
1097
  if (requiresOpenOrdersSync) {
@@ -1279,6 +1283,30 @@ class DEXBot {
1279
1283
  await this._processFillsWithBootstrapMode(chainOrders);
1280
1284
  }
1281
1285
  this.manager.finishBootstrap();
1286
+ // Refresh account totals after bootstrap to eliminate the timing
1287
+ // gap between the initial balance fetch and grid operations (sync,
1288
+ // reconcile, fills). Without this, the first maintenance cycle sees
1289
+ // a fund drift (expected at bootstrap) and triggers an unnecessary
1290
+ // invariant violation + full recovery cycle.
1291
+ // Bound by a timeout to avoid blocking _fillProcessingLock on a
1292
+ // flaky node. If the fetch times out, continue with cached values;
1293
+ // the next periodic maintenance cycle will retry.
1294
+ const FETCH_TIMEOUT_MS = 30000;
1295
+ let _fetchTimeoutHandle;
1296
+ try {
1297
+ await Promise.race([
1298
+ this.manager.fetchAccountTotals(),
1299
+ new Promise((_, reject) => {
1300
+ _fetchTimeoutHandle = setTimeout(() => reject(new Error('timeout')), FETCH_TIMEOUT_MS);
1301
+ })
1302
+ ]);
1303
+ }
1304
+ catch (fetchErr) {
1305
+ this._log(`[STARTUP] [${this.config?.botKey || 'unknown'}] fetchAccountTotals ${fetchErr.message === 'timeout' ? 'timed out' : 'failed'} (${fetchErr.message}). Continuing with cached account totals.`, 'warn');
1306
+ }
1307
+ finally {
1308
+ clearTimeout(_fetchTimeoutHandle);
1309
+ }
1282
1310
  // Perform initial grid maintenance (thresholds, divergence, spread, health)
1283
1311
  // Consolidated into shared logic to ensure consistent behavior at boot and runtime.
1284
1312
  // CRITICAL: Pass lockAlreadyHeld since we're inside _fillProcessingLock.acquire()
@@ -1504,11 +1532,11 @@ class DEXBot {
1504
1532
  try {
1505
1533
  // BOOTSTRAP OPTIMIZATION: During bootstrap, prioritize fill processing over grid-wide checks
1506
1534
  // Process fills immediately with side-only rebalancing (no expensive full grid recalculations)
1507
- if (this.manager._state.isBootstrapping()) {
1535
+ if (this.manager.isBootstrapping()) {
1508
1536
  // During bootstrap: skip lock contention checks, process fills directly
1509
1537
  let bootstrapSkipped = false;
1510
1538
  await this.manager._fillProcessingLock.acquire(async () => {
1511
- if (!this.manager._state.isBootstrapping()) {
1539
+ if (!this.manager.isBootstrapping()) {
1512
1540
  // Bootstrap finished while waiting for the lock — no
1513
1541
  // work to do, but the iteration is still healthy.
1514
1542
  bootstrapSkipped = true;
@@ -1542,10 +1570,12 @@ class DEXBot {
1542
1570
  // fill cycle. It is re-set when orphan fills are credited,
1543
1571
  // and consumed (set to null) on the next fund-invariant check
1544
1572
  // in accounting.ts, which widens tolerance by 5x while set.
1573
+ // Written to this.manager (not this) because accounting.ts
1574
+ // reads from the OrderManager reference (mgr).
1545
1575
  // Also cleared by _performStateRecovery after a fresh chain
1546
1576
  // fetch. The timestamp value itself is not compared against a
1547
1577
  // window — it acts as a consume-on-read boolean.
1548
- this._orphanFillsCreditedAt = null;
1578
+ this.manager._orphanFillsCreditedAt = null;
1549
1579
  while (this._incomingFillQueue.length > 0) {
1550
1580
  const batchStartTime = Date.now();
1551
1581
  // Track max queue depth
@@ -1610,9 +1640,9 @@ class DEXBot {
1610
1640
  requiresOpenOrdersSync = true;
1611
1641
  }
1612
1642
  // Record orphan fill credit timestamp for fund invariant
1613
- // tolerance widening. Accounting checks recency via
1614
- // _checkOrphanFillRecency() instead of a cross-module flag.
1615
- this._orphanFillsCreditedAt = Date.now();
1643
+ // tolerance widening. Written to this.manager because
1644
+ // accounting.ts reads from the OrderManager (mgr).
1645
+ this.manager._orphanFillsCreditedAt = Date.now();
1616
1646
  // Don't add to validFills - we can't do rebalancing without a grid slot
1617
1647
  // But the funds are now credited, preventing fund invariant violation
1618
1648
  continue;
@@ -1620,7 +1650,7 @@ class DEXBot {
1620
1650
  // Process both maker and taker fills for our grid orders
1621
1651
  // Grid validation ensures we only process fills belonging to our account
1622
1652
  // Taker fills are included because the bot may execute market orders or act as taker
1623
- const roleStr = fillOp.is_maker ? 'maker' : 'taker';
1653
+ const roleStr = fillOp.is_maker !== false ? 'maker' : 'taker';
1624
1654
  this.manager.logger.log(`Processing ${roleStr} fill for order ${fillOp.order_id}`, 'debug');
1625
1655
  const fillKey = buildFillKey(fill);
1626
1656
  if (!fillKey) {
@@ -1727,6 +1757,12 @@ class DEXBot {
1727
1757
  const sortedBlocks = [...fillsByBlock.keys()].sort((a, b) => a - b);
1728
1758
  const accumulatedOrders = [];
1729
1759
  let anyRequiresSync = false;
1760
+ // Capture whether any fill was skipped during validation
1761
+ // (e.g. missing history ID) and set the sync flag. The
1762
+ // per-block reset below would lose this state since those
1763
+ // fills were filtered out of validFills and never enter
1764
+ // any block group.
1765
+ const initialRequiresSync = requiresOpenOrdersSync;
1730
1766
  for (const blockNum of sortedBlocks) {
1731
1767
  // Reset requiresOpenOrdersSync per block group so one
1732
1768
  // block's history-id gap doesn't force the next block
@@ -1738,12 +1774,24 @@ class DEXBot {
1738
1774
  if (requiresOpenOrdersSync)
1739
1775
  anyRequiresSync = true;
1740
1776
  }
1741
- requiresOpenOrdersSync = anyRequiresSync;
1777
+ // Preserve both per-block sync flags and the initial flag
1778
+ // from filtered-out fills (missing history ID).
1779
+ requiresOpenOrdersSync = anyRequiresSync || initialRequiresSync;
1742
1780
  if (fillsWithoutBlock.length > 0) {
1743
1781
  this.manager.logger.log(`[FILL-BLOCK] Processing ${fillsWithoutBlock.length} fill(s) without block info`, 'debug');
1744
1782
  const noBlockResult = await processValidFills(fillsWithoutBlock);
1745
1783
  accumulatedOrders.push(...noBlockResult);
1746
1784
  }
1785
+ // If a fill was filtered out during validation (e.g. missing
1786
+ // history ID) and no block group triggered the open-orders
1787
+ // sync, run it now so the grid state is reconciled.
1788
+ if (requiresOpenOrdersSync && !anyRequiresSync) {
1789
+ this.manager.logger.log('[FILL-BLOCK] Running open-orders sync for fills with missing history identifiers', 'warn');
1790
+ const fallbackOrders = await processValidFills([]);
1791
+ accumulatedOrders.push(...fallbackOrders);
1792
+ // Flag consumed — variable goes out of scope on
1793
+ // next while iteration.
1794
+ }
1747
1795
  allFilledOrders = accumulatedOrders;
1748
1796
  // 4. Handle Price Corrections
1749
1797
  if (ordersNeedingCorrection.length > 0) {
@@ -1978,20 +2026,26 @@ class DEXBot {
1978
2026
  if (trackedFillKey && !this._isNewFillKey(trackedFillKey, processedFillKeys, '[BOOTSTRAP]', fillOp.order_id)) {
1979
2027
  continue;
1980
2028
  }
1981
- const accountingResult = await this._applyReplaySafeTrackedFillAccounting(fill, fillOp, {
1982
- context: 'BOOTSTRAP',
1983
- replayMessage: (op) => `[BOOTSTRAP] Replay detected for ${op.order_id}; skipping duplicate bootstrap rebalance`
1984
- });
1985
- if (accountingResult.status === 'missing_key') {
1986
- requiresOpenOrdersSync = true;
1987
- continue;
2029
+ this.manager.lockOrders([gridOrder.id]);
2030
+ try {
2031
+ const accountingResult = await this._applyReplaySafeTrackedFillAccounting(fill, fillOp, {
2032
+ context: 'BOOTSTRAP',
2033
+ replayMessage: (op) => `[BOOTSTRAP] Replay detected for ${op.order_id}; skipping duplicate bootstrap rebalance`
2034
+ });
2035
+ if (accountingResult.status === 'missing_key') {
2036
+ requiresOpenOrdersSync = true;
2037
+ continue;
2038
+ }
2039
+ if (accountingResult.status !== 'applied') {
2040
+ continue;
2041
+ }
2042
+ validFills.push({ ...fill, gridOrder });
2043
+ const fillType = gridOrder.type === ORDER_TYPES.BUY ? 'BUY' : 'SELL';
2044
+ this._log(`[BOOTSTRAP] Fill detected: ${fillType} order (${fillOp.is_maker !== false ? 'maker' : 'taker'})`);
1988
2045
  }
1989
- if (accountingResult.status !== 'applied') {
1990
- continue;
2046
+ finally {
2047
+ this.manager.unlockOrders([gridOrder.id]);
1991
2048
  }
1992
- validFills.push({ ...fill, gridOrder });
1993
- const fillType = gridOrder.type === ORDER_TYPES.BUY ? 'BUY' : 'SELL';
1994
- this._log(`[BOOTSTRAP] Fill detected: ${fillType} order (${fillOp.is_maker ? 'maker' : 'taker'})`);
1995
2049
  }
1996
2050
  if (requiresOpenOrdersSync) {
1997
2051
  this._log('[BOOTSTRAP] Falling back to open-orders sync for fill(s) missing replay-safe history identifiers', 'warn');
@@ -2628,7 +2682,11 @@ class DEXBot {
2628
2682
  fingerprint: entry.fingerprint
2629
2683
  });
2630
2684
  if (match) {
2631
- adopted.push({ slotId: entry.slotId, chainOrderId: match.id });
2685
+ adopted.push({
2686
+ slotId: entry.slotId,
2687
+ chainOrderId: match.id,
2688
+ orderType: entry.orderType || entry.order?.type,
2689
+ });
2632
2690
  this.manager._pendingBroadcasts.delete(entry.fingerprint);
2633
2691
  }
2634
2692
  else {
@@ -2665,7 +2723,11 @@ class DEXBot {
2665
2723
  fingerprint: entry.fingerprint
2666
2724
  });
2667
2725
  if (match) {
2668
- adopted.push({ slotId: entry.slotId, chainOrderId: match.id });
2726
+ adopted.push({
2727
+ slotId: entry.slotId,
2728
+ chainOrderId: match.id,
2729
+ orderType: entry.orderType || entry.order?.type,
2730
+ });
2669
2731
  this.manager.logger.log(`[COW][UNCERTAIN] CREATE re-adopted after ${recheckRound} block(s): ` +
2670
2732
  `${entry.slotId}->${match.id}`, 'info');
2671
2733
  }
@@ -2748,27 +2810,27 @@ class DEXBot {
2748
2810
  this.manager.logger.log(`[COW][UNCERTAIN] Auto-cancel pass failed: ${orphanErr?.message || orphanErr}`, 'warn');
2749
2811
  }
2750
2812
  // Boundary shift recovery: the COW batch that should have committed the
2751
- // boundary shift from this fill cycle failed before commit. Every planned
2752
- // CREATE in the batch represents a fill whose opposite-side boundary step
2753
- // was lost — regardless of whether the individual CREATE was later adopted
2754
- // on chain (adopted) or not (discarded). Manually adjust manager.boundaryIdx
2755
- // so the next COW cycle (triggered by the next fill or periodic maintenance)
2756
- // generates replacement opposite-side orders using the correct boundary.
2813
+ // boundary shift from this fill cycle failed before commit. Only COUNT
2814
+ // ADOPTED CREATEs — orders that actually landed on-chain. Discarded
2815
+ // CREATEs represent orders that never existed, so the grid must NOT
2816
+ // shift the boundary for them. The next fill cycle's
2817
+ // calculateTargetGrid → deriveTargetBoundary will recompute the correct
2818
+ // boundary from scratch for any discarded slots.
2757
2819
  //
2758
2820
  // NOTE: Direct mutation of manager.boundaryIdx outside a COW commit
2759
2821
  // violates the invariant stated in grid.ts:1217-1220 (boundary must only
2760
2822
  // be updated atomically inside _commitWorkingGrid). This is intentional
2761
2823
  // here because the COW commit already failed — the invariant was already
2762
2824
  // broken by the broadcast uncertainty, and the adjustment only restores
2763
- // the state to what the successful commit would have produced.
2764
- if (hadRotation && this.manager && pending.length > 0) {
2825
+ // the state to what the successful commit would have produced for
2826
+ // orders that are confirmed on-chain.
2827
+ if (hadRotation && this.manager && adopted.length > 0) {
2765
2828
  let boundaryShift = 0;
2766
- for (const entry of pending) {
2767
- const orderType = entry.orderType || entry.order?.type;
2768
- if (orderType === ORDER_TYPES.SELL) {
2829
+ for (const entry of adopted) {
2830
+ if (entry.orderType === ORDER_TYPES.SELL) {
2769
2831
  boundaryShift--; // SELL CREATE → fill was BUY → boundary LEFT
2770
2832
  }
2771
- else if (orderType === ORDER_TYPES.BUY) {
2833
+ else if (entry.orderType === ORDER_TYPES.BUY) {
2772
2834
  boundaryShift++; // BUY CREATE → fill was SELL → boundary RIGHT
2773
2835
  }
2774
2836
  }
@@ -2780,10 +2842,10 @@ class DEXBot {
2780
2842
  if (newIdx !== oldIdx) {
2781
2843
  this.manager.boundaryIdx = newIdx;
2782
2844
  this.manager.logger.log(`[COW][UNCERTAIN] Boundary adjusted by ${boundaryShift} ` +
2783
- `(${adopted.length} adopted / ${discarded.length} discarded CREATE(s)): ` +
2845
+ `(${adopted.length} adopted CREATE(s), ${discarded.length} discarded): ` +
2784
2846
  `${oldIdx} → ${newIdx}. Next COW cycle will generate replacement opposite-side orders.`, 'warn');
2785
2847
  if (typeof this.manager._markGridDirty === 'function') {
2786
- this.manager._markGridDirty(`boundary adjustment after ${pending.length} planned CREATE(s)`);
2848
+ this.manager._markGridDirty();
2787
2849
  }
2788
2850
  }
2789
2851
  }
@@ -3324,7 +3386,7 @@ class DEXBot {
3324
3386
  if (this._credentialRecoveryInFlight || !this._credentialRecoveryNeeded || this._shuttingDown) {
3325
3387
  return;
3326
3388
  }
3327
- if (this.manager?._state?.isBootstrapping?.() || this.manager?._state?.isBroadcastingActive?.()) {
3389
+ if (this.manager?.isBootstrapping?.() || this.manager?.isBroadcastingActive?.()) {
3328
3390
  if (!this._credentialRecoveryDeferredTimer) {
3329
3391
  this.manager?.logger?.log?.('[CREDENTIAL] Deferring credential recovery until startup/broadcast activity is idle.', 'info');
3330
3392
  this._credentialRecoveryDeferredTimer = setTimeout(() => {