dexbot 1.1.13 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +1 -0
  2. package/dist/market_adapter/market_adapter.js +1 -1
  3. package/dist/market_adapter/market_adapter.js.map +1 -1
  4. package/dist/modules/bitshares-native/lru_cache.d.ts +20 -0
  5. package/dist/modules/bitshares-native/lru_cache.d.ts.map +1 -0
  6. package/dist/modules/bitshares-native/lru_cache.js +45 -0
  7. package/dist/modules/bitshares-native/lru_cache.js.map +1 -0
  8. package/dist/modules/bitshares-native/resolvers.d.ts +2 -18
  9. package/dist/modules/bitshares-native/resolvers.d.ts.map +1 -1
  10. package/dist/modules/bitshares-native/resolvers.js +2 -41
  11. package/dist/modules/bitshares-native/resolvers.js.map +1 -1
  12. package/dist/modules/bitshares-native/serial/chain_constants.d.ts +1 -0
  13. package/dist/modules/bitshares-native/serial/chain_constants.d.ts.map +1 -1
  14. package/dist/modules/bitshares-native/serial/chain_constants.js +3 -0
  15. package/dist/modules/bitshares-native/serial/chain_constants.js.map +1 -1
  16. package/dist/modules/bitshares-native/signing_client.d.ts.map +1 -1
  17. package/dist/modules/bitshares-native/signing_client.js +12 -1
  18. package/dist/modules/bitshares-native/signing_client.js.map +1 -1
  19. package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
  20. package/dist/modules/bitshares-native/subscriptions.js +42 -3
  21. package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
  22. package/dist/modules/bitshares-native/transport.d.ts.map +1 -1
  23. package/dist/modules/bitshares-native/transport.js +77 -27
  24. package/dist/modules/bitshares-native/transport.js.map +1 -1
  25. package/dist/modules/bitshares-native/tx/builder.d.ts.map +1 -1
  26. package/dist/modules/bitshares-native/tx/builder.js +26 -16
  27. package/dist/modules/bitshares-native/tx/builder.js.map +1 -1
  28. package/dist/modules/bitshares-native/tx/tx_cache.d.ts +17 -0
  29. package/dist/modules/bitshares-native/tx/tx_cache.d.ts.map +1 -0
  30. package/dist/modules/bitshares-native/tx/tx_cache.js +62 -0
  31. package/dist/modules/bitshares-native/tx/tx_cache.js.map +1 -0
  32. package/dist/modules/bot_settings.d.ts.map +1 -1
  33. package/dist/modules/bot_settings.js +2 -1
  34. package/dist/modules/bot_settings.js.map +1 -1
  35. package/dist/modules/chain_orders.d.ts +4 -1
  36. package/dist/modules/chain_orders.d.ts.map +1 -1
  37. package/dist/modules/chain_orders.js +30 -1
  38. package/dist/modules/chain_orders.js.map +1 -1
  39. package/dist/modules/config.d.ts +6 -0
  40. package/dist/modules/config.d.ts.map +1 -1
  41. package/dist/modules/config.js +11 -0
  42. package/dist/modules/config.js.map +1 -1
  43. package/dist/modules/constants.d.ts +17 -1
  44. package/dist/modules/constants.d.ts.map +1 -1
  45. package/dist/modules/constants.js +84 -18
  46. package/dist/modules/constants.js.map +1 -1
  47. package/dist/modules/credit_runtime.d.ts.map +1 -1
  48. package/dist/modules/credit_runtime.js +40 -29
  49. package/dist/modules/credit_runtime.js.map +1 -1
  50. package/dist/modules/dexbot_class.d.ts +16 -12
  51. package/dist/modules/dexbot_class.d.ts.map +1 -1
  52. package/dist/modules/dexbot_class.js +183 -78
  53. package/dist/modules/dexbot_class.js.map +1 -1
  54. package/dist/modules/dexbot_credential_client.d.ts +1 -0
  55. package/dist/modules/dexbot_credential_client.d.ts.map +1 -1
  56. package/dist/modules/dexbot_credential_client.js +15 -6
  57. package/dist/modules/dexbot_credential_client.js.map +1 -1
  58. package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -5
  59. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  60. package/dist/modules/dexbot_maintenance_runtime.js +78 -42
  61. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  62. package/dist/modules/key_store.d.ts.map +1 -1
  63. package/dist/modules/key_store.js +2 -0
  64. package/dist/modules/key_store.js.map +1 -1
  65. package/dist/modules/launcher/child_env.d.ts.map +1 -1
  66. package/dist/modules/launcher/child_env.js +1 -0
  67. package/dist/modules/launcher/child_env.js.map +1 -1
  68. package/dist/modules/launcher/launch_modes.d.ts +2 -0
  69. package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
  70. package/dist/modules/launcher/launch_modes.js +7 -6
  71. package/dist/modules/launcher/launch_modes.js.map +1 -1
  72. package/dist/modules/node_manager.d.ts +9 -0
  73. package/dist/modules/node_manager.d.ts.map +1 -1
  74. package/dist/modules/node_manager.js +35 -17
  75. package/dist/modules/node_manager.js.map +1 -1
  76. package/dist/modules/order/accounting.d.ts.map +1 -1
  77. package/dist/modules/order/accounting.js +56 -13
  78. package/dist/modules/order/accounting.js.map +1 -1
  79. package/dist/modules/order/async_lock.d.ts +33 -4
  80. package/dist/modules/order/async_lock.d.ts.map +1 -1
  81. package/dist/modules/order/async_lock.js +69 -7
  82. package/dist/modules/order/async_lock.js.map +1 -1
  83. package/dist/modules/order/grid.d.ts +435 -450
  84. package/dist/modules/order/grid.d.ts.map +1 -1
  85. package/dist/modules/order/grid.js +1621 -1548
  86. package/dist/modules/order/grid.js.map +1 -1
  87. package/dist/modules/order/grid_reconcile.d.ts +3 -73
  88. package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
  89. package/dist/modules/order/grid_reconcile.js +11 -843
  90. package/dist/modules/order/grid_reconcile.js.map +1 -1
  91. package/dist/modules/order/grid_reconcile_internal.d.ts +8 -0
  92. package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -0
  93. package/dist/modules/order/grid_reconcile_internal.js +771 -0
  94. package/dist/modules/order/grid_reconcile_internal.js.map +1 -0
  95. package/dist/modules/order/logger.d.ts +1 -1
  96. package/dist/modules/order/logger.d.ts.map +1 -1
  97. package/dist/modules/order/logger.js +26 -2
  98. package/dist/modules/order/logger.js.map +1 -1
  99. package/dist/modules/order/manager.d.ts +40 -3
  100. package/dist/modules/order/manager.d.ts.map +1 -1
  101. package/dist/modules/order/manager.js +97 -15
  102. package/dist/modules/order/manager.js.map +1 -1
  103. package/dist/modules/order/runner.js +2 -2
  104. package/dist/modules/order/runner.js.map +1 -1
  105. package/dist/modules/order/strategy.js +3 -3
  106. package/dist/modules/order/strategy.js.map +1 -1
  107. package/dist/modules/order/sync_engine.d.ts +1 -1
  108. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  109. package/dist/modules/order/sync_engine.js +121 -29
  110. package/dist/modules/order/sync_engine.js.map +1 -1
  111. package/dist/modules/order/utils/order.d.ts +17 -0
  112. package/dist/modules/order/utils/order.d.ts.map +1 -1
  113. package/dist/modules/order/utils/order.js +56 -19
  114. package/dist/modules/order/utils/order.js.map +1 -1
  115. package/dist/modules/order/utils/system.js +4 -4
  116. package/dist/modules/order/utils/system.js.map +1 -1
  117. package/dist/modules/order/utils/validate.d.ts.map +1 -1
  118. package/dist/modules/order/utils/validate.js +6 -8
  119. package/dist/modules/order/utils/validate.js.map +1 -1
  120. package/dist/scripts/print_grid.js +3 -3
  121. package/dist/scripts/print_grid.js.map +1 -1
  122. package/dist/scripts/test-credit-renewal.js +1 -1
  123. package/dist/scripts/test-credit-renewal.js.map +1 -1
  124. package/dist/unlock.d.ts.map +1 -1
  125. package/dist/unlock.js +26 -12
  126. package/dist/unlock.js.map +1 -1
  127. package/package.json +14 -4
@@ -71,7 +71,7 @@ const { ProcessedFillStore, PROCESSED_FILL_PERSISTENCE_MODES } = require('./orde
71
71
  const DexbotFillRuntime = require('./dexbot_fill_runtime');
72
72
  const DexbotMaintenanceRuntime = require('./dexbot_maintenance_runtime');
73
73
  const CreditRuntime = require('./credit_runtime');
74
- const { ORDER_STATES, ORDER_TYPES, REBALANCE_STATES, COW_ACTIONS, TIMING, PIPELINE_TIMING, MAINTENANCE, FILL_PROCESSING, DAEMON_CODES, } = require('./constants');
74
+ const { ORDER_STATES, ORDER_TYPES, REBALANCE_STATES, COW_ACTIONS, TIMING, PIPELINE_TIMING, GRID_LIMITS, MAINTENANCE, FILL_PROCESSING, DAEMON_CODES, } = require('./constants');
75
75
  const { PATHS, getRecalculateTriggerFile } = require('./paths');
76
76
  const { attemptResumePersistedGridByPriceMatch, decideStartupGridAction, reconcileGridOrders } = require('./order/grid_reconcile');
77
77
  const { AccountOrders } = require('./account_orders');
@@ -127,6 +127,7 @@ class DEXBot {
127
127
  _batchInFlight;
128
128
  _recoverySyncInFlight;
129
129
  _lastTargetedDriftSyncAt;
130
+ _lightweightSyncCheckAt;
130
131
  _targetedDriftSyncCooldownMs;
131
132
  _maintenanceCooldownCycles;
132
133
  _lastGridActivityAt;
@@ -218,6 +219,7 @@ class DEXBot {
218
219
  this._batchInFlight = false;
219
220
  this._recoverySyncInFlight = false;
220
221
  this._lastTargetedDriftSyncAt = 0;
222
+ this._lightweightSyncCheckAt = 0;
221
223
  this._targetedDriftSyncCooldownMs = this.config.timing.TARGETED_DRIFT_SYNC_COOLDOWN_MS;
222
224
  this._maintenanceCooldownCycles = 0;
223
225
  this._lastGridActivityAt = 0;
@@ -246,25 +248,28 @@ class DEXBot {
246
248
  */
247
249
  _validateStartupConfig(config) {
248
250
  const errors = [];
249
- // Validate startPrice is numeric or valid string mode
250
- const startPrice = config.startPrice;
251
- const validPriceModes = ['pool', 'book'];
252
- const isPriceNumeric = typeof startPrice === 'number' && Number.isFinite(startPrice) && startPrice > 0;
253
- const isPriceMode = typeof startPrice === 'string' && validPriceModes.includes(startPrice.toLowerCase());
254
- if (!isPriceNumeric && !isPriceMode) {
255
- errors.push(`startPrice must be a positive number or valid mode (${validPriceModes.join('/')}), got: ${startPrice}`);
256
- }
257
- // Validate assetA and assetB are present
258
- if (!config.assetA || typeof config.assetA !== 'string') {
259
- errors.push(`assetA must be a non-empty string, got: ${config.assetA}`);
260
- }
261
- if (!config.assetB || typeof config.assetB !== 'string') {
262
- errors.push(`assetB must be a non-empty string, got: ${config.assetB}`);
263
- }
264
- // Validate incrementPercent
265
- const increment = config.incrementPercent;
266
- if (!Number.isFinite(increment) || increment <= 0 || increment > 100) {
267
- errors.push(`incrementPercent must be between 0 and 100, got: ${increment}`);
251
+ // Skip trading field validation in credit-only mode
252
+ if (!config.creditOnly) {
253
+ // Validate startPrice is numeric or valid string mode
254
+ const startPrice = config.startPrice;
255
+ const validPriceModes = ['pool', 'book'];
256
+ const isPriceNumeric = typeof startPrice === 'number' && Number.isFinite(startPrice) && startPrice > 0;
257
+ const isPriceMode = typeof startPrice === 'string' && validPriceModes.includes(startPrice.toLowerCase());
258
+ if (!isPriceNumeric && !isPriceMode) {
259
+ errors.push(`startPrice must be a positive number or valid mode (${validPriceModes.join('/')}), got: ${startPrice}`);
260
+ }
261
+ // Validate assetA and assetB are present
262
+ if (!config.assetA || typeof config.assetA !== 'string') {
263
+ errors.push(`assetA must be a non-empty string, got: ${config.assetA}`);
264
+ }
265
+ if (!config.assetB || typeof config.assetB !== 'string') {
266
+ errors.push(`assetB must be a non-empty string, got: ${config.assetB}`);
267
+ }
268
+ // Validate incrementPercent
269
+ const increment = config.incrementPercent;
270
+ if (!Number.isFinite(increment) || increment <= 0 || increment > 100) {
271
+ errors.push(`incrementPercent must be between 0 and 100, got: ${increment}`);
272
+ }
268
273
  }
269
274
  // Throw all validation errors at once
270
275
  if (errors.length > 0) {
@@ -373,7 +378,7 @@ class DEXBot {
373
378
  this.manager.logger.log(`Triggering state recovery sync (${reason})...`, 'info');
374
379
  await this.manager.fetchAccountTotals(this.accountId);
375
380
  const openOrders = await chainOrders.readOpenOrders(this.accountId);
376
- await this.manager.syncFromOpenOrders(openOrders, { skipAccounting: true, fillLockAlreadyHeld: true, protectCommittedOrders: true });
381
+ await this.manager.syncFromOpenOrders(openOrders, { skipAccounting: true });
377
382
  if (typeof this.manager.persistGrid === 'function') {
378
383
  await this.manager.persistGrid();
379
384
  }
@@ -575,14 +580,16 @@ class DEXBot {
575
580
  const boundaryIdx = this.accountOrders.loadBoundaryIdx(true);
576
581
  // 2. Load into manager (same path as startup at line 1340)
577
582
  await Grid.loadGrid(this.manager, persistedGrid, boundaryIdx);
583
+ // Gap 1: Grid snapshot sanity check — shared logic with startup path.
584
+ if (await this._rejectCorruptedGridSnapshot('recovery')) {
585
+ return { success: false, reason: 'corrupted grid snapshot rejected (fund drift)' };
586
+ }
578
587
  // 3. Read current chain state
579
588
  const chainOpenOrders = await chainOrders.readOpenOrders(accountRef);
580
589
  // 4. Reconcile
581
590
  if (chainOpenOrders.length > 0 && this.manager?.syncFromOpenOrders) {
582
591
  await this.manager.syncFromOpenOrders(chainOpenOrders, {
583
592
  skipAccounting: true,
584
- fillLockAlreadyHeld: true,
585
- protectCommittedOrders: true
586
593
  });
587
594
  }
588
595
  // 5. Persist the reconciled state
@@ -598,6 +605,35 @@ class DEXBot {
598
605
  return { success: false, reason: err.message };
599
606
  }
600
607
  }
608
+ /**
609
+ * Reject a corrupted grid snapshot when catastrophic fund drift is detected.
610
+ * Shared between startup and recovery paths to avoid duplicating the
611
+ * drift-ratio math and snapshot-clearing logic.
612
+ *
613
+ * @param {'startup'|'recovery'} context - Controls log prefix.
614
+ * @returns {Promise<boolean>} True if the snapshot was rejected (cleared).
615
+ */
616
+ async _rejectCorruptedGridSnapshot(context) {
617
+ if (!this.manager?.checkFundDriftAfterFills)
618
+ return false;
619
+ const driftCheck = this.manager.checkFundDriftAfterFills();
620
+ if (driftCheck.isValid)
621
+ return false;
622
+ const tag = context === 'recovery' ? '[RECOVERY][SNAPSHOT-REJECT]' : '[SNAPSHOT-REJECT]';
623
+ this._warn(`${tag} Corrupted grid snapshot detected: ` +
624
+ `drift sell=${driftCheck.driftSell.toFixed(2)} buy=${driftCheck.driftBuy.toFixed(2)}. ` +
625
+ `Deleting corrupted snapshot.`);
626
+ if (this.accountOrders && typeof this.accountOrders.clearGrid === 'function') {
627
+ try {
628
+ await this.accountOrders.clearGrid();
629
+ this._warn(`${tag} Corrupted grid snapshot deleted.`);
630
+ }
631
+ catch (clearErr) {
632
+ this._warn(`${tag} Failed to delete corrupted snapshot: ${clearErr.message}`);
633
+ }
634
+ }
635
+ return true;
636
+ }
601
637
  /**
602
638
  * Attempt to repair size-drift for specific order IDs by reading their
603
639
  * current on-chain state and correcting the local grid directly.
@@ -784,13 +820,14 @@ class DEXBot {
784
820
  _buildOrphanFillFallbackKey(fill) {
785
821
  return DexbotFillRuntime.buildOrphanFillFallbackKey(this, fill);
786
822
  }
787
- _isNewFillKey(fillKey, processedFillKeys, label = '') {
823
+ _isNewFillKey(fillKey, processedFillKeys, label = '', orderId = '') {
788
824
  const now = Date.now();
789
825
  if (this._recentlyQueuedFills.has(fillKey)) {
790
826
  const lastProcessed = this._recentlyQueuedFills.get(fillKey);
791
827
  if (now - lastProcessed < this._fillDedupeWindowMs) {
792
828
  if (label) {
793
- this.manager?.logger?.log?.(`${label} Skipping duplicate fill (processed ${now - lastProcessed}ms ago)`, 'debug');
829
+ const idSuffix = orderId ? ` for ${orderId}` : '';
830
+ this.manager?.logger?.log?.(`${label} Skipping duplicate fill${idSuffix} (processed ${now - lastProcessed}ms ago)`, 'debug');
794
831
  }
795
832
  return false;
796
833
  }
@@ -918,7 +955,7 @@ class DEXBot {
918
955
  const chainOpenOrders = await chainOrders.readOpenOrders(this.accountId);
919
956
  if (this._shuttingDown)
920
957
  return;
921
- const syncResult = await this.manager.synchronizeWithChain(chainOpenOrders, 'readOpenOrders', { fillLockAlreadyHeld: true });
958
+ const syncResult = await this.manager.synchronizeWithChain(chainOpenOrders, 'readOpenOrders');
922
959
  if (this._shuttingDown)
923
960
  return;
924
961
  if (syncResult?.filledOrders?.length > 0) {
@@ -935,7 +972,6 @@ class DEXBot {
935
972
  await this._cancelDustOrders({
936
973
  buy: reconnectHealth.buyDustOrders,
937
974
  sell: reconnectHealth.sellDustOrders,
938
- fillLockAlreadyHeld: true,
939
975
  });
940
976
  }
941
977
  catch (_dustErr) {
@@ -1015,7 +1051,7 @@ class DEXBot {
1015
1051
  if (!orphanFillKey) {
1016
1052
  orphanFillKey = this._buildOrphanFillFallbackKey(fill);
1017
1053
  }
1018
- if (orphanFillKey && !this._isNewFillKey(orphanFillKey, processedFillKeys, '[POST-RESET]')) {
1054
+ if (orphanFillKey && !this._isNewFillKey(orphanFillKey, processedFillKeys, '[POST-RESET]', fillOp.order_id)) {
1019
1055
  continue;
1020
1056
  }
1021
1057
  this._log(`[POST-RESET] Processing funds for unknown order ${fillOp.order_id} (not in grid but crediting proceeds)`, 'warn');
@@ -1031,7 +1067,7 @@ class DEXBot {
1031
1067
  }
1032
1068
  this._log(`[POST-RESET] Processing fill for ${gridOrder.type} order ${gridOrder.id} at price ${gridOrder.price}`);
1033
1069
  const trackedFillKey = buildFillKey(fill);
1034
- if (trackedFillKey && !this._isNewFillKey(trackedFillKey, processedFillKeys, '[POST-RESET]')) {
1070
+ if (trackedFillKey && !this._isNewFillKey(trackedFillKey, processedFillKeys, '[POST-RESET]', fillOp.order_id)) {
1035
1071
  continue;
1036
1072
  }
1037
1073
  const accountingResult = await this._applyReplaySafeTrackedFillAccounting(fill, fillOp, {
@@ -1057,7 +1093,7 @@ class DEXBot {
1057
1093
  if (requiresOpenOrdersSync) {
1058
1094
  this._log('[POST-RESET] Falling back to open-orders sync for fill(s) missing replay-safe history identifiers', 'warn');
1059
1095
  const postResetChainOpenOrders = await chainOrders.readOpenOrders(this.accountId);
1060
- const syncResult = await this.manager.syncFromOpenOrders(postResetChainOpenOrders, { fillLockAlreadyHeld: true });
1096
+ const syncResult = await this.manager.syncFromOpenOrders(postResetChainOpenOrders);
1061
1097
  if (syncResult.filledOrders?.length > 0) {
1062
1098
  await this._processFillsWithBatching(syncResult.filledOrders, new Set(), '[POST-RESET] open-orders fallback');
1063
1099
  }
@@ -1088,7 +1124,6 @@ class DEXBot {
1088
1124
  await this._cancelDustOrders({
1089
1125
  buy: postResetHealth.buyDustOrders,
1090
1126
  sell: postResetHealth.sellDustOrders,
1091
- fillLockAlreadyHeld: true,
1092
1127
  });
1093
1128
  }
1094
1129
  catch (_dustErr) {
@@ -1184,7 +1219,7 @@ class DEXBot {
1184
1219
  if (Array.isArray(chainOpenOrders) && chainOpenOrders.length > 0) {
1185
1220
  this._log('Generating new grid and syncing with existing on-chain orders...');
1186
1221
  await Grid.initializeGrid(this.manager);
1187
- await this.manager.syncFromOpenOrders(chainOpenOrders, { skipAccounting: true, fillLockAlreadyHeld: true });
1222
+ await this.manager.syncFromOpenOrders(chainOpenOrders, { skipAccounting: true });
1188
1223
  const rebalanceResult = await reconcileGridOrders({
1189
1224
  manager: this.manager,
1190
1225
  config: this.config,
@@ -1192,7 +1227,6 @@ class DEXBot {
1192
1227
  privateKey: this.privateKey,
1193
1228
  chainOrders,
1194
1229
  chainOpenOrders,
1195
- fillLockAlreadyHeld: true,
1196
1230
  });
1197
1231
  await this._executeBatchIfNeeded(rebalanceResult, 'startup reconcile (regenerated grid)');
1198
1232
  }
@@ -1206,7 +1240,7 @@ class DEXBot {
1206
1240
  this._log('Found active session. Loading and syncing existing grid.');
1207
1241
  await Grid.loadGrid(this.manager, persistedGrid, persistedBoundaryIdx);
1208
1242
  let startupChainOpenOrders = chainOpenOrders;
1209
- const syncResult = await this.manager.syncFromOpenOrders(startupChainOpenOrders, { skipAccounting: true, fillLockAlreadyHeld: true });
1243
+ const syncResult = await this.manager.syncFromOpenOrders(startupChainOpenOrders, { skipAccounting: true });
1210
1244
  // Process price corrections queued during startup sync.
1211
1245
  // These are not picked up by _consumeFillQueue until a fill
1212
1246
  // arrives, which may never come for an idle market.
@@ -1220,7 +1254,7 @@ class DEXBot {
1220
1254
  // Refresh open orders so startup reconcile works with post-batch chain reality
1221
1255
  // and avoids reconciling against a stale pre-batch snapshot.
1222
1256
  startupChainOpenOrders = await chainOrders.readOpenOrders(this.accountId);
1223
- await this.manager.synchronizeWithChain(startupChainOpenOrders, 'readOpenOrders', { fillLockAlreadyHeld: true });
1257
+ await this.manager.synchronizeWithChain(startupChainOpenOrders, 'readOpenOrders');
1224
1258
  }
1225
1259
  }
1226
1260
  const rebalanceResult = await reconcileGridOrders({
@@ -1230,11 +1264,12 @@ class DEXBot {
1230
1264
  privateKey: this.privateKey,
1231
1265
  chainOrders,
1232
1266
  chainOpenOrders: startupChainOpenOrders,
1233
- fillLockAlreadyHeld: true,
1234
1267
  });
1235
1268
  await this._executeBatchIfNeeded(rebalanceResult, 'startup reconcile (loaded grid)');
1236
1269
  // Dust state is no longer persisted — cancelled immediately on detection.
1237
1270
  await this._persistAndRecoverIfNeeded();
1271
+ // Gap 1: Grid snapshot sanity check — shared logic with recovery path.
1272
+ await this._rejectCorruptedGridSnapshot('startup');
1238
1273
  }
1239
1274
  // Drain any fills that arrived during startup while still in bootstrap
1240
1275
  // mode. Safe to call directly since we already hold _fillProcessingLock
@@ -1247,13 +1282,12 @@ class DEXBot {
1247
1282
  // Perform initial grid maintenance (thresholds, divergence, spread, health)
1248
1283
  // Consolidated into shared logic to ensure consistent behavior at boot and runtime.
1249
1284
  // CRITICAL: Pass lockAlreadyHeld since we're inside _fillProcessingLock.acquire()
1250
- await this._runGridMaintenance('startup', { fillLockAlreadyHeld: true });
1285
+ await this._runGridMaintenance('startup');
1251
1286
  // Cancel any dust from a previous bot lifetime immediately.
1252
1287
  const startupHealth = await this.manager.checkGridHealth(this.updateOrdersOnChainPlan.bind(this));
1253
1288
  await this._cancelDustOrders({
1254
1289
  buy: startupHealth.buyDustOrders,
1255
1290
  sell: startupHealth.sellDustOrders,
1256
- fillLockAlreadyHeld: true,
1257
1291
  });
1258
1292
  this._log('Bootstrap phase complete - fill processing resumed', 'info');
1259
1293
  }
@@ -1265,7 +1299,7 @@ class DEXBot {
1265
1299
  await this._setupTriggerFileDetection();
1266
1300
  await this._setupCreditRuntime();
1267
1301
  await this._refreshAndSyncCreditRuntime();
1268
- await this._runCreditRuntimeMaintenance('startup', { fillLockAlreadyHeld: true });
1302
+ await this._runCreditRuntimeMaintenance('startup');
1269
1303
  this._setupBlockchainFetchInterval();
1270
1304
  this._setupCreditWatchdogInterval();
1271
1305
  this._setupCredentialDaemonWatchdogInterval();
@@ -1309,14 +1343,14 @@ class DEXBot {
1309
1343
  }
1310
1344
  try {
1311
1345
  let openOrders = await chainOrders.readOpenOrders(this.accountId);
1312
- const syncResult = await this.manager.synchronizeWithChain(openOrders, 'readOpenOrders', { fillLockAlreadyHeld: true });
1346
+ const syncResult = await this.manager.synchronizeWithChain(openOrders, 'readOpenOrders');
1313
1347
  let aborted = false;
1314
1348
  if (syncResult?.filledOrders?.length > 0) {
1315
1349
  this._log(`[SYNC-CHAIN] ${syncResult.filledOrders.length} filled order(s) found during ${tag}`, 'info');
1316
1350
  const batchResult = await this._processFillsWithBatching(syncResult.filledOrders, new Set(), `${tag} sync-fill`);
1317
1351
  if (!batchResult?.aborted) {
1318
1352
  openOrders = await chainOrders.readOpenOrders(this.accountId);
1319
- await this.manager.synchronizeWithChain(openOrders, 'readOpenOrders', { fillLockAlreadyHeld: true });
1353
+ await this.manager.synchronizeWithChain(openOrders, 'readOpenOrders');
1320
1354
  }
1321
1355
  else {
1322
1356
  aborted = true;
@@ -1564,7 +1598,7 @@ class DEXBot {
1564
1598
  if (!orphanFillKey) {
1565
1599
  orphanFillKey = this._buildOrphanFillFallbackKey(fill);
1566
1600
  }
1567
- if (orphanFillKey && !this._isNewFillKey(orphanFillKey, processedFillKeys, '[ORPHAN-FILL]')) {
1601
+ if (orphanFillKey && !this._isNewFillKey(orphanFillKey, processedFillKeys, '[ORPHAN-FILL]', fillOp.order_id)) {
1568
1602
  continue;
1569
1603
  }
1570
1604
  this.manager.logger.log(`[ORPHAN-FILL] Processing funds for unknown order ${fillOp.order_id} (not in grid but crediting proceeds)`, 'warn');
@@ -1594,7 +1628,7 @@ class DEXBot {
1594
1628
  requiresOpenOrdersSync = true;
1595
1629
  continue;
1596
1630
  }
1597
- if (!this._isNewFillKey(fillKey, processedFillKeys, '[FILL]')) {
1631
+ if (!this._isNewFillKey(fillKey, processedFillKeys, '[FILL]', fillOp.order_id)) {
1598
1632
  continue;
1599
1633
  }
1600
1634
  validFills.push(fill);
@@ -1657,7 +1691,7 @@ class DEXBot {
1657
1691
  }
1658
1692
  this.manager.logger.log(`Syncing ${fillsToSync.length} fill(s) (open orders mode)`, 'info');
1659
1693
  const chainOpenOrders = await chainOrders.readOpenOrders(this.account);
1660
- const resultOpenOrders = await this.manager.syncFromOpenOrders(chainOpenOrders, { fillLockAlreadyHeld: true });
1694
+ const resultOpenOrders = await this.manager.syncFromOpenOrders(chainOpenOrders);
1661
1695
  // Dust is handled by post-fill detection below.
1662
1696
  if (resultOpenOrders.filledOrders)
1663
1697
  resolvedOrders.push(...resultOpenOrders.filledOrders);
@@ -1790,7 +1824,6 @@ class DEXBot {
1790
1824
  const dustCancelResult = await this._cancelDustOrders({
1791
1825
  buy: healthResult.buyDustOrders,
1792
1826
  sell: healthResult.sellDustOrders,
1793
- fillLockAlreadyHeld: true,
1794
1827
  });
1795
1828
  if (dustCancelResult?.batchResult?.aborted) {
1796
1829
  abortedFillCycle = true;
@@ -1804,7 +1837,7 @@ class DEXBot {
1804
1837
  // After: Grid maintenance waits for isPipelineEmpty() before structural changes
1805
1838
  // Run only when the cycle contains at least one full fill.
1806
1839
  if (shouldRunPostFillChecks && !abortedFillCycle) {
1807
- await this._runGridMaintenance('post-fill', { fillLockAlreadyHeld: true });
1840
+ await this._runGridMaintenance('post-fill');
1808
1841
  }
1809
1842
  }
1810
1843
  else if (pendingFillKeysForCurrentCycle.size > 0) {
@@ -1929,7 +1962,7 @@ class DEXBot {
1929
1962
  if (!orphanFillKey) {
1930
1963
  orphanFillKey = this._buildOrphanFillFallbackKey(fill);
1931
1964
  }
1932
- if (orphanFillKey && !this._isNewFillKey(orphanFillKey, processedFillKeys, '[BOOTSTRAP]')) {
1965
+ if (orphanFillKey && !this._isNewFillKey(orphanFillKey, processedFillKeys, '[BOOTSTRAP]', fillOp.order_id)) {
1933
1966
  continue;
1934
1967
  }
1935
1968
  this.manager.logger.log(`[BOOTSTRAP] Processing funds for unknown order ${fillOp.order_id} (not in grid but crediting proceeds)`, 'warn');
@@ -1942,7 +1975,7 @@ class DEXBot {
1942
1975
  continue;
1943
1976
  }
1944
1977
  const trackedFillKey = buildFillKey(fill);
1945
- if (trackedFillKey && !this._isNewFillKey(trackedFillKey, processedFillKeys, '[BOOTSTRAP]')) {
1978
+ if (trackedFillKey && !this._isNewFillKey(trackedFillKey, processedFillKeys, '[BOOTSTRAP]', fillOp.order_id)) {
1946
1979
  continue;
1947
1980
  }
1948
1981
  const accountingResult = await this._applyReplaySafeTrackedFillAccounting(fill, fillOp, {
@@ -1963,7 +1996,7 @@ class DEXBot {
1963
1996
  if (requiresOpenOrdersSync) {
1964
1997
  this._log('[BOOTSTRAP] Falling back to open-orders sync for fill(s) missing replay-safe history identifiers', 'warn');
1965
1998
  const bootstrapChainOpenOrders = await chainOrders.readOpenOrders(this.accountId);
1966
- const syncResult = await this.manager.syncFromOpenOrders(bootstrapChainOpenOrders, { fillLockAlreadyHeld: true });
1999
+ const syncResult = await this.manager.syncFromOpenOrders(bootstrapChainOpenOrders);
1967
2000
  if (syncResult.filledOrders?.length > 0) {
1968
2001
  const queuedOrderIds = new Set(validFills.map(fill => fill?.gridOrder?.orderId).filter(Boolean));
1969
2002
  for (const filledOrder of syncResult.filledOrders) {
@@ -2201,8 +2234,6 @@ class DEXBot {
2201
2234
  const openOrders = await chainOrders.readOpenOrders(accountRef);
2202
2235
  const recoveryResult = await this.manager.syncFromOpenOrders(openOrders, {
2203
2236
  skipAccounting: false,
2204
- fillLockAlreadyHeld: true,
2205
- protectCommittedOrders: true
2206
2237
  });
2207
2238
  this._preserveMissingCreateBlockersAfterRecovery(preRecoveryMissingCreateBlockers, recoveryResult);
2208
2239
  // Persist any master grid mutations from the recovery sync. The
@@ -2546,13 +2577,10 @@ class DEXBot {
2546
2577
  * @returns {Promise<Object>} Result object compatible with batch return shape
2547
2578
  */
2548
2579
  async _reconcileAfterUncertainBroadcast(err, opContexts, options = {}) {
2549
- if (options.fillLockAlreadyHeld !== true &&
2550
- this.manager?._fillProcessingLock &&
2551
- typeof this.manager._fillProcessingLock.acquire === 'function') {
2552
- return this.manager._fillProcessingLock.acquire(async () => (this._reconcileAfterUncertainBroadcast(err, opContexts, {
2553
- ...options,
2554
- fillLockAlreadyHeld: true
2555
- })));
2580
+ if (this.manager?._fillProcessingLock &&
2581
+ typeof this.manager._fillProcessingLock.acquire === 'function' &&
2582
+ !this.manager._fillProcessingLock.isReentrant()) {
2583
+ return this.manager._fillProcessingLock.acquire(() => this._reconcileAfterUncertainBroadcast(err, opContexts, options));
2556
2584
  }
2557
2585
  return this._reconcileAfterUncertainBroadcastImpl(err, opContexts, options);
2558
2586
  }
@@ -2665,8 +2693,6 @@ class DEXBot {
2665
2693
  try {
2666
2694
  await this.manager.syncFromOpenOrders(latestSnapshot, {
2667
2695
  skipAccounting: true,
2668
- fillLockAlreadyHeld: true,
2669
- protectCommittedOrders: true
2670
2696
  });
2671
2697
  hadRotation = true;
2672
2698
  }
@@ -2678,6 +2704,15 @@ class DEXBot {
2678
2704
  if (fallbackResult.success) {
2679
2705
  hadRotation = true;
2680
2706
  }
2707
+ else {
2708
+ this.manager.logger.log(`[COW][UNCERTAIN] Persisted grid reload failed: ${fallbackResult.reason}. ` +
2709
+ `Escalating to structural resync.`, 'warn');
2710
+ if (typeof this.manager.requestStructuralGridResync === 'function') {
2711
+ this.manager.requestStructuralGridResync('cow-uncertain-recovery-failed', { reason: fallbackResult.reason }).catch((escalateErr) => {
2712
+ this.manager.logger.log(`[COW][UNCERTAIN] Escalation to structural resync failed: ${escalateErr.message}`, 'error');
2713
+ });
2714
+ }
2715
+ }
2681
2716
  }
2682
2717
  }
2683
2718
  // 4. Log structured summary.
@@ -2712,9 +2747,52 @@ class DEXBot {
2712
2747
  catch (orphanErr) {
2713
2748
  this.manager.logger.log(`[COW][UNCERTAIN] Auto-cancel pass failed: ${orphanErr?.message || orphanErr}`, 'warn');
2714
2749
  }
2750
+ // 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.
2757
+ //
2758
+ // NOTE: Direct mutation of manager.boundaryIdx outside a COW commit
2759
+ // violates the invariant stated in grid.ts:1217-1220 (boundary must only
2760
+ // be updated atomically inside _commitWorkingGrid). This is intentional
2761
+ // here because the COW commit already failed — the invariant was already
2762
+ // 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) {
2765
+ let boundaryShift = 0;
2766
+ for (const entry of pending) {
2767
+ const orderType = entry.orderType || entry.order?.type;
2768
+ if (orderType === ORDER_TYPES.SELL) {
2769
+ boundaryShift--; // SELL CREATE → fill was BUY → boundary LEFT
2770
+ }
2771
+ else if (orderType === ORDER_TYPES.BUY) {
2772
+ boundaryShift++; // BUY CREATE → fill was SELL → boundary RIGHT
2773
+ }
2774
+ }
2775
+ if (boundaryShift !== 0) {
2776
+ const oldIdx = this.manager.boundaryIdx;
2777
+ if (typeof oldIdx === 'number') {
2778
+ const maxIdx = Math.max(0, (this.manager.orders?.size || 1) - 1);
2779
+ const newIdx = Math.max(0, Math.min(maxIdx, oldIdx + boundaryShift));
2780
+ if (newIdx !== oldIdx) {
2781
+ this.manager.boundaryIdx = newIdx;
2782
+ this.manager.logger.log(`[COW][UNCERTAIN] Boundary adjusted by ${boundaryShift} ` +
2783
+ `(${adopted.length} adopted / ${discarded.length} discarded CREATE(s)): ` +
2784
+ `${oldIdx} → ${newIdx}. Next COW cycle will generate replacement opposite-side orders.`, 'warn');
2785
+ if (typeof this.manager._markGridDirty === 'function') {
2786
+ this.manager._markGridDirty(`boundary adjustment after ${pending.length} planned CREATE(s)`);
2787
+ }
2788
+ }
2789
+ }
2790
+ }
2791
+ }
2715
2792
  // Persist master grid mutations from the reconciliation sync and any
2716
2793
  // orphan auto-cancellation that ran above. These apply outside the COW
2717
- // broadcast path and would otherwise be in-memory only.
2794
+ // broadcast path and would otherwise be in-memory only. The boundary
2795
+ // adjustment above is also persisted here.
2718
2796
  if (typeof this.manager?.persistGrid === 'function') {
2719
2797
  await this.manager.persistGrid();
2720
2798
  }
@@ -2852,8 +2930,6 @@ class DEXBot {
2852
2930
  if (freshChain.length > 0 && this.manager?.syncFromOpenOrders) {
2853
2931
  await this.manager.syncFromOpenOrders(freshChain, {
2854
2932
  skipAccounting: true,
2855
- fillLockAlreadyHeld: true,
2856
- protectCommittedOrders: true
2857
2933
  });
2858
2934
  }
2859
2935
  }
@@ -3270,7 +3346,7 @@ class DEXBot {
3270
3346
  this._resumeGridPersistenceAfterCredentialRecovery('credential recovery started');
3271
3347
  const runRecovery = async () => {
3272
3348
  await this._triggerStateRecoverySync('credential daemon restored');
3273
- await this._runGridMaintenance('credential-recovery', { fillLockAlreadyHeld: true });
3349
+ await this._runGridMaintenance('credential-recovery');
3274
3350
  };
3275
3351
  if (this.manager?._fillProcessingLock) {
3276
3352
  await this.manager._fillProcessingLock.acquire(runRecovery);
@@ -3609,6 +3685,22 @@ class DEXBot {
3609
3685
  };
3610
3686
  }
3611
3687
  const hasCreateActions = actions.some(action => action.type === COW_ACTIONS.CREATE);
3688
+ // Gap 5: Recovery exhausted — block all CREATES. The bot has exhausted
3689
+ // its recovery attempt budget and needs a fill or sync cycle to reset.
3690
+ // Existing orders remain active and are monitored, but no new orders
3691
+ // are placed until recovery resets.
3692
+ if (hasCreateActions && this.manager?._recoveryExhaustedAt) {
3693
+ const exhaustedAge = Date.now() - this.manager._recoveryExhaustedAt;
3694
+ this.manager.logger.log?.(`[RECOVERY-EXHAUSTED] Blocking ${actions.filter(a => a.type === COW_ACTIONS.CREATE).length} CREATE(s) ` +
3695
+ `(exhausted ${(exhaustedAge / 1000).toFixed(0)}s ago). ` +
3696
+ `Waiting for next fill or sync cycle to reset recovery state.`, 'warn');
3697
+ return {
3698
+ executed: false,
3699
+ aborted: true,
3700
+ reason: 'RECOVERY_EXHAUSTED',
3701
+ hadRotation: false
3702
+ };
3703
+ }
3612
3704
  const unmatchedChainOrders = Array.isArray(this.manager?._lastUnmatchedChainOrders)
3613
3705
  ? this.manager._lastUnmatchedChainOrders
3614
3706
  : [];
@@ -3654,7 +3746,7 @@ class DEXBot {
3654
3746
  this.manager.logger.log(`[COW] Failed to cancel unmatched chain order: ${r.reason?.message || r.reason}`, 'warn');
3655
3747
  }
3656
3748
  }
3657
- if (cancelledUnmatched >= cancelable.length) {
3749
+ if (cancelable.length > 0 && cancelledUnmatched >= cancelable.length) {
3658
3750
  this.manager.logger.log(`[COW] Cancelled all ${cancelledUnmatched} unmatched chain order(s); proceeding with CREATE batch.`, 'warn');
3659
3751
  // Optimistic clear: the cancel txs are sent but not yet
3660
3752
  // confirmed. If a cancel silently fails on chain, the
@@ -3663,7 +3755,7 @@ class DEXBot {
3663
3755
  // is idempotent and the re-detection is non-blocking.
3664
3756
  this.manager._lastUnmatchedChainOrders = [];
3665
3757
  }
3666
- else {
3758
+ else if (cancelable.length > 0) {
3667
3759
  this.manager.logger.log(`[COW] Cancelled ${cancelledUnmatched}/${cancelable.length} unmatched chain order(s); ` +
3668
3760
  `remaining must be handled by structural reconciliation.`, 'warn');
3669
3761
  }
@@ -3711,7 +3803,7 @@ class DEXBot {
3711
3803
  batchId: this._currentBatchId || null,
3712
3804
  payload: null,
3713
3805
  timeoutMs: null
3714
- }), [], { fillLockAlreadyHeld: true });
3806
+ }), []);
3715
3807
  }
3716
3808
  catch (recoverErr) {
3717
3809
  this.manager.logger.log(`[COW] Recovery from pending broadcasts failed: ${recoverErr?.message || recoverErr}`, 'error');
@@ -4071,7 +4163,7 @@ class DEXBot {
4071
4163
  // throwing would re-enter the catch loop on the next attempt and
4072
4164
  // potentially double-publish.
4073
4165
  if (err instanceof BroadcastUncertainError) {
4074
- return await this._reconcileAfterUncertainBroadcast(err, opContexts, { fillLockAlreadyHeld: true });
4166
+ return await this._reconcileAfterUncertainBroadcast(err, opContexts);
4075
4167
  }
4076
4168
  // Handle hard abort
4077
4169
  const hardAbortResult = await this._handleBatchHardAbort(err, 'COW batch processing', operations.length);
@@ -4377,6 +4469,10 @@ class DEXBot {
4377
4469
  */
4378
4470
  async _runStartupSequence() {
4379
4471
  try {
4472
+ if (this.config.creditOnly) {
4473
+ await this._runCreditOnlyStartup();
4474
+ return;
4475
+ }
4380
4476
  const startupState = await this._initializeStartupState();
4381
4477
  await this._finishStartupSequence(startupState);
4382
4478
  }
@@ -4386,13 +4482,20 @@ class DEXBot {
4386
4482
  throw err;
4387
4483
  }
4388
4484
  }
4485
+ async _runCreditOnlyStartup() {
4486
+ await this._setupCreditRuntime();
4487
+ await this._refreshAndSyncCreditRuntime();
4488
+ await this._runCreditRuntimeMaintenance('startup');
4489
+ this._setupCreditWatchdogInterval();
4490
+ this._setupCredentialDaemonWatchdogInterval();
4491
+ this._log('DEXBot started (credit-only mode)');
4492
+ }
4389
4493
  /**
4390
4494
  * Perform periodic grid checks: fund thresholds, spread condition, grid health.
4391
4495
  * Called by the periodic blockchain fetch interval to check if grid needs updates.
4392
4496
  *
4393
4497
  * IMPORTANT: This method MUST only be called from within _fillProcessingLock.acquire()
4394
- * (specifically from _setupBlockchainFetchInterval). It passes fillLockAlreadyHeld
4395
- * to avoid deadlock with _consumeFillQueue which uses the same lock ordering.
4498
+ * (specifically from _setupBlockchainFetchInterval).
4396
4499
  *
4397
4500
  * @private
4398
4501
  */
@@ -4539,14 +4642,17 @@ class DEXBot {
4539
4642
  return { skipped: true, reason: 'grid resync unavailable' };
4540
4643
  }
4541
4644
  const message = reason ? `[CR-RESET] ${reason}` : '[CR-RESET] grid reset requested';
4542
- // Manual/programmatic resets should advance the persisted center baseline
4543
- // before rebuilding the grid, unless a caller explicitly disables it.
4544
4645
  this._log(`${message}; rebuilding grid from fresh on-chain state`, 'info');
4545
4646
  const resetOptions = {
4546
4647
  ...options,
4547
4648
  refreshCenterPrice: options.refreshCenterPrice !== false,
4548
4649
  };
4549
- if (options.fillLockAlreadyHeld || !this.manager._fillProcessingLock) {
4650
+ // Skip acquire if no lock exists (no fill processing to serialize against)
4651
+ // or if the caller already holds the lock (re-entrant). In the latter case
4652
+ // the lock's isReentrant() check prevents queueing and runs inline.
4653
+ // Otherwise, wait on the queue — this is the intended path for callers
4654
+ // outside the fill-processing context that need exclusive access.
4655
+ if (!this.manager._fillProcessingLock || this.manager._fillProcessingLock.isReentrant()) {
4550
4656
  return this._performGridResync(resetOptions);
4551
4657
  }
4552
4658
  return this.manager._fillProcessingLock.acquire(async () => this._performGridResync(resetOptions));
@@ -4616,6 +4722,7 @@ class DEXBot {
4616
4722
  fillProcessingLockActive: this.manager?._fillProcessingLock?.isLocked() || false,
4617
4723
  divergenceLockActive: this.manager?._divergenceLock?.isLocked() || false,
4618
4724
  shadowLocksActive: this.manager?.shadowOrderIds?.size || 0,
4725
+ recoveryExhaustedAt: this.manager?._recoveryExhaustedAt || null,
4619
4726
  recentFillsTracked: this._recentlyProcessedFills.size
4620
4727
  };
4621
4728
  }
@@ -4657,12 +4764,12 @@ class DEXBot {
4657
4764
  * Cancel dust partial orders immediately — no maps, no timers, no delay.
4658
4765
  * Each dust order is cancelled on chain and its slot rotated through the
4659
4766
  * synthetic-fill pipeline.
4660
- * @param {{ buy: Array, sell: Array, fillLockAlreadyHeld?: boolean }} options
4767
+ * @param {{ buy: Array, sell: Array }} options
4661
4768
  * @returns {Promise<{cancelledCount: number, batchResult: {aborted: boolean}|null}>}
4662
4769
  * @private
4663
4770
  */
4664
- async _cancelDustOrders({ buy: buyDust = [], sell: sellDust = [], fillLockAlreadyHeld = false } = {}) {
4665
- return DexbotMaintenanceRuntime.cancelDustOrders(this, { buy: buyDust, sell: sellDust, fillLockAlreadyHeld });
4771
+ async _cancelDustOrders({ buy: buyDust = [], sell: sellDust = [] } = {}) {
4772
+ return DexbotMaintenanceRuntime.cancelDustOrders(this, { buy: buyDust, sell: sellDust });
4666
4773
  }
4667
4774
  /**
4668
4775
  * Set up the periodic dust health check interval.
@@ -4687,7 +4794,6 @@ class DEXBot {
4687
4794
  await this._cancelDustOrders({
4688
4795
  buy: health.buyDustOrders,
4689
4796
  sell: health.sellDustOrders,
4690
- fillLockAlreadyHeld: true,
4691
4797
  });
4692
4798
  });
4693
4799
  }
@@ -4713,7 +4819,6 @@ class DEXBot {
4713
4819
  *
4714
4820
  * LOCK ORDERING:
4715
4821
  * - Canonical order: _fillProcessingLock → _divergenceLock
4716
- * - This function handles lock acquisition based on fillLockAlreadyHeld parameter
4717
4822
  * - When called from post-fill context, fill lock is already held
4718
4823
  * - When called from periodic context, both locks must be acquired
4719
4824
  * - Matches the order used in _consumeFillQueue to prevent deadlocks