dexbot 1.4.2 → 1.4.4

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 (79) hide show
  1. package/README.md +1 -0
  2. package/dist/modules/account_bots.d.ts.map +1 -1
  3. package/dist/modules/account_bots.js +29 -1
  4. package/dist/modules/account_bots.js.map +1 -1
  5. package/dist/modules/constants.d.ts +1 -0
  6. package/dist/modules/constants.d.ts.map +1 -1
  7. package/dist/modules/constants.js +5 -0
  8. package/dist/modules/constants.js.map +1 -1
  9. package/dist/modules/dexbot_cow_runtime.d.ts +30 -0
  10. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  11. package/dist/modules/dexbot_cow_runtime.js +228 -7
  12. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  13. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  14. package/dist/modules/dexbot_maintenance_runtime.js +31 -25
  15. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  16. package/dist/modules/launcher/credential_daemon.d.ts.map +1 -1
  17. package/dist/modules/launcher/credential_daemon.js +3 -4
  18. package/dist/modules/launcher/credential_daemon.js.map +1 -1
  19. package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
  20. package/dist/modules/launcher/market_adapter_runtime.js +2 -4
  21. package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
  22. package/dist/modules/launcher/market_adapter_watchdog.d.ts.map +1 -1
  23. package/dist/modules/launcher/market_adapter_watchdog.js +4 -6
  24. package/dist/modules/launcher/market_adapter_watchdog.js.map +1 -1
  25. package/dist/modules/logger.js +0 -1
  26. package/dist/modules/logger.js.map +1 -1
  27. package/dist/modules/order/accounting.d.ts +2 -2
  28. package/dist/modules/order/accounting.d.ts.map +1 -1
  29. package/dist/modules/order/accounting.js +3 -6
  30. package/dist/modules/order/accounting.js.map +1 -1
  31. package/dist/modules/order/grid.d.ts +1 -1
  32. package/dist/modules/order/grid.d.ts.map +1 -1
  33. package/dist/modules/order/grid.js +83 -69
  34. package/dist/modules/order/grid.js.map +1 -1
  35. package/dist/modules/order/grid_reconcile.d.ts +3 -9
  36. package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
  37. package/dist/modules/order/grid_reconcile.js +80 -48
  38. package/dist/modules/order/grid_reconcile.js.map +1 -1
  39. package/dist/modules/order/logger.d.ts.map +1 -1
  40. package/dist/modules/order/logger.js +2 -8
  41. package/dist/modules/order/logger.js.map +1 -1
  42. package/dist/modules/order/manager.d.ts +38 -7
  43. package/dist/modules/order/manager.d.ts.map +1 -1
  44. package/dist/modules/order/manager.js +89 -130
  45. package/dist/modules/order/manager.js.map +1 -1
  46. package/dist/modules/order/strategy.js +0 -1
  47. package/dist/modules/order/strategy.js.map +1 -1
  48. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  49. package/dist/modules/order/sync_engine.js +1 -2
  50. package/dist/modules/order/sync_engine.js.map +1 -1
  51. package/dist/modules/order/utils/math.d.ts +26 -1
  52. package/dist/modules/order/utils/math.d.ts.map +1 -1
  53. package/dist/modules/order/utils/math.js +107 -54
  54. package/dist/modules/order/utils/math.js.map +1 -1
  55. package/dist/modules/order/utils/order.d.ts.map +1 -1
  56. package/dist/modules/order/utils/order.js +10 -20
  57. package/dist/modules/order/utils/order.js.map +1 -1
  58. package/dist/modules/order/utils/system.d.ts +10 -4
  59. package/dist/modules/order/utils/system.d.ts.map +1 -1
  60. package/dist/modules/order/utils/system.js +47 -28
  61. package/dist/modules/order/utils/system.js.map +1 -1
  62. package/dist/modules/order/utils/timeout.d.ts +25 -0
  63. package/dist/modules/order/utils/timeout.d.ts.map +1 -0
  64. package/dist/modules/order/utils/timeout.js +49 -0
  65. package/dist/modules/order/utils/timeout.js.map +1 -0
  66. package/dist/modules/order/utils/validate.d.ts +8 -1
  67. package/dist/modules/order/utils/validate.d.ts.map +1 -1
  68. package/dist/modules/order/utils/validate.js +33 -8
  69. package/dist/modules/order/utils/validate.js.map +1 -1
  70. package/dist/modules/order/utils/withPoolRef.d.ts +6 -0
  71. package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -0
  72. package/dist/modules/order/utils/withPoolRef.js +129 -0
  73. package/dist/modules/order/utils/withPoolRef.js.map +1 -0
  74. package/dist/modules/types.d.ts +1 -0
  75. package/dist/modules/types.d.ts.map +1 -1
  76. package/dist/unlock.d.ts.map +1 -1
  77. package/dist/unlock.js +2 -4
  78. package/dist/unlock.js.map +1 -1
  79. package/package.json +2 -1
@@ -1107,6 +1107,142 @@ function buildCowResultFromPlan(bot, plan) {
1107
1107
  * @param {Set<string>} skippedSlotIds
1108
1108
  * @param {number} [skippedCount=0]
1109
1109
  */
1110
+ /**
1111
+ * Pre-apply rotation state transitions to the working grid before commit.
1112
+ * This makes the COW commit truly atomic for structural changes — source slots
1113
+ * are cleared to VIRTUAL and destination slots are activated with the inherited
1114
+ * orderId before the working grid is committed to master, eliminating the need
1115
+ * for post-commit structural patching in processBatchResults.
1116
+ *
1117
+ * Only slot-to-slot rotations (newGridId exists) need pre-application;
1118
+ * in-place rotations already have their size/price changes in the working grid.
1119
+ */
1120
+ function applyRotationTransitionsToWorkingGrid(bot, workingGrid, executedContexts) {
1121
+ if (!workingGrid || !executedContexts)
1122
+ return;
1123
+ for (const ctx of executedContexts) {
1124
+ if (ctx.kind !== 'rotation' || !ctx.rotation?.newGridId)
1125
+ continue;
1126
+ const { rotation } = ctx;
1127
+ const { oldOrder, newGridId, newPrice, newSize, type } = rotation;
1128
+ // Source slot → VIRTUAL (if it's a different slot)
1129
+ if (oldOrder?.id && oldOrder.id !== newGridId) {
1130
+ const sourceSlot = workingGrid.get(oldOrder.id);
1131
+ if (sourceSlot && sourceSlot.orderId) {
1132
+ workingGrid.set(oldOrder.id, {
1133
+ ...sourceSlot,
1134
+ state: ORDER_STATES.VIRTUAL,
1135
+ orderId: null,
1136
+ rawOnChain: null
1137
+ });
1138
+ bot.manager.logger.log(`[COW] Pre-applied rotation: source ${oldOrder.id} → VIRTUAL (order ${sourceSlot.orderId} moved to ${newGridId})`, 'debug');
1139
+ }
1140
+ }
1141
+ // Destination slot → ACTIVE with inherited orderId from source
1142
+ const destSlot = workingGrid.get(newGridId);
1143
+ if (destSlot) {
1144
+ workingGrid.set(newGridId, {
1145
+ ...destSlot,
1146
+ id: newGridId,
1147
+ type,
1148
+ size: newSize,
1149
+ price: newPrice,
1150
+ state: ORDER_STATES.ACTIVE,
1151
+ // oldOrder?.orderId is the authoritative source: the rotation
1152
+ // moved this orderId from the source slot. destSlot.orderId
1153
+ // is only a fallback for edge cases where the destination
1154
+ // already held a prior committed ID (e.g. a partial commit
1155
+ // left a stale reference). The source-of-truth is always the
1156
+ // original order being rotated.
1157
+ orderId: oldOrder?.orderId || destSlot.orderId || null
1158
+ });
1159
+ bot.manager.logger.log(`[COW] Pre-applied rotation: dest ${newGridId} → ACTIVE (orderId=${oldOrder?.orderId || destSlot.orderId || 'none'})`, 'debug');
1160
+ }
1161
+ }
1162
+ }
1163
+ /**
1164
+ * Sleep for a given number of milliseconds.
1165
+ */
1166
+ function sleep(ms) {
1167
+ return new Promise(resolve => setTimeout(resolve, ms));
1168
+ }
1169
+ /**
1170
+ * Poll the chain after an uncertain broadcast to check if CREATE operations
1171
+ * were actually accepted. Uses fingerprint matching against readOpenOrders.
1172
+ * Falls back to reconciliation if polling cannot confirm within retries.
1173
+ *
1174
+ * We specifically confirm CREATE operations because they leave a detectable
1175
+ * footprint on the chain (new order with matching fingerprint). UPDATEs and
1176
+ * CANCELs modify existing orders and cannot be reliably distinguished from
1177
+ * "not yet visible" state by simple polling.
1178
+ *
1179
+ * @returns {{ allConfirmed: boolean, confirmed: Array, unconfirmed: Array }}
1180
+ */
1181
+ async function pollChainForConfirmation(bot, opContexts, options = {}) {
1182
+ const maxPollRetries = options.maxPollRetries || 4;
1183
+ const pollIntervalMs = options.pollIntervalMs || 1500;
1184
+ // Only CREATE operations can be confirmed by polling (they appear as new orders on chain)
1185
+ const createContexts = opContexts.filter((ctx) => ctx && ctx.kind === 'create' && ctx.finalInts && ctx.order);
1186
+ if (createContexts.length === 0) {
1187
+ return { allConfirmed: false, confirmed: [], unconfirmed: [...opContexts] };
1188
+ }
1189
+ const accountRef = bot.accountId || bot.account?.id || bot.account;
1190
+ let remaining = [...createContexts];
1191
+ for (let attempt = 1; attempt <= maxPollRetries; attempt++) {
1192
+ try {
1193
+ const chainSnapshot = await chainOrders.readOpenOrders(accountRef);
1194
+ if (!Array.isArray(chainSnapshot) || chainSnapshot.length === 0) {
1195
+ if (attempt < maxPollRetries) {
1196
+ await sleep(pollIntervalMs);
1197
+ }
1198
+ continue;
1199
+ }
1200
+ const stillUnconfirmed = [];
1201
+ for (const ctx of remaining) {
1202
+ const match = findChainOrderForSlot(bot, chainSnapshot, ctx.order.id, {
1203
+ sell: ctx.finalInts.sell,
1204
+ receive: ctx.finalInts.receive,
1205
+ orderType: ctx.order.type,
1206
+ fingerprint: createContexts.length > 0
1207
+ ? buildCreateOpFingerprint({
1208
+ side: ctx.order.type,
1209
+ assetA: bot.manager?.assets?.assetA?.id,
1210
+ assetB: bot.manager?.assets?.assetB?.id,
1211
+ sellInt: ctx.finalInts.sell,
1212
+ receiveInt: ctx.finalInts.receive,
1213
+ slotId: ctx.order.id
1214
+ })
1215
+ : undefined
1216
+ });
1217
+ if (match) {
1218
+ bot.manager.logger.log(`[COW][POLL] Confirmed CREATE for slot ${ctx.order.id} on chain as ${match.id}`, 'debug');
1219
+ }
1220
+ else {
1221
+ stillUnconfirmed.push(ctx);
1222
+ }
1223
+ }
1224
+ if (stillUnconfirmed.length === 0) {
1225
+ const confirmed = createContexts;
1226
+ bot.manager.logger.log(`[COW][POLL] All ${confirmed.length} CREATE(s) confirmed on chain after ${attempt} poll(s)`, 'info');
1227
+ return { allConfirmed: true, confirmed, unconfirmed: [] };
1228
+ }
1229
+ remaining = stillUnconfirmed;
1230
+ if (attempt < maxPollRetries) {
1231
+ await sleep(pollIntervalMs);
1232
+ }
1233
+ }
1234
+ catch (pollErr) {
1235
+ bot.manager.logger.log(`[COW][POLL] Chain read attempt ${attempt}/${maxPollRetries} failed: ${getErrorMessage(pollErr)}`, 'warn');
1236
+ if (attempt < maxPollRetries) {
1237
+ await sleep(pollIntervalMs);
1238
+ }
1239
+ }
1240
+ }
1241
+ const confirmed = createContexts.filter((ctx) => !remaining.includes(ctx));
1242
+ bot.manager.logger.log(`[COW][POLL] ${confirmed.length}/${createContexts.length} CREATE(s) confirmed after ${maxPollRetries} polls; ` +
1243
+ `${remaining.length} unconfirmed. Falling back to reconciliation.`, 'warn');
1244
+ return { allConfirmed: false, confirmed, unconfirmed: remaining };
1245
+ }
1110
1246
  function restoreSkippedUpdateSlotsInWorkingGrid(bot, workingGrid, skippedSlotIds, skippedCount = 0) {
1111
1247
  if (!workingGrid || !skippedSlotIds || skippedSlotIds.size === 0) {
1112
1248
  return;
@@ -1163,13 +1299,42 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult) {
1163
1299
  bot.manager.logger.log(`[COW] Rejecting CREATE for slot ${violation.targetId}: ${reason} ` +
1164
1300
  `(type=${violation.currentType}, state=${violation.currentState})`, 'error');
1165
1301
  }
1166
- return {
1167
- executed: false,
1168
- aborted: true,
1169
- reason: 'CREATE_SLOT_OCCUPIED',
1170
- violations: createSlotValidation.violations,
1171
- hadRotation: false
1172
- };
1302
+ // Differentiate violation types:
1303
+ // * slot_occupied — hard constraint, not a false positive (the
1304
+ // target slot literally has a placed order). Abort the batch.
1305
+ // * price_collision / chain_orphan_collision / same_batch_price_collision
1306
+ // — tolerance-based; can false-positive on low-precision assets
1307
+ // where calculatePriceTolerance exceeds the grid increment.
1308
+ // Skip only the violating CREATEs so the rest of the batch
1309
+ // (valid CREATEs, CANCELs, UPDATEs) still proceeds.
1310
+ const hasHardOccupiedViolation = createSlotValidation.violations.some((v) => v.reason === 'slot_occupied');
1311
+ if (hasHardOccupiedViolation) {
1312
+ return {
1313
+ executed: false,
1314
+ aborted: true,
1315
+ reason: 'CREATE_SLOT_OCCUPIED',
1316
+ violations: createSlotValidation.violations,
1317
+ hadRotation: false
1318
+ };
1319
+ }
1320
+ const violatingIds = createSlotValidation.violatingTargetIds;
1321
+ const filteredActions = actions.filter((action) => {
1322
+ if (action.type !== COW_ACTIONS.CREATE)
1323
+ return true;
1324
+ const targetId = action.id || action.order?.id;
1325
+ return !violatingIds.has(targetId);
1326
+ });
1327
+ actions.length = 0;
1328
+ actions.push(...filteredActions);
1329
+ // All violations at this point are tolerance-based; slot_occupied
1330
+ // would have aborted above.
1331
+ bot.manager.logger.log(`[COW] Filtered ${createSlotValidation.violations.length} tolerance-violating CREATE(s); ` +
1332
+ `${actions.length} action(s) remaining in batch`, 'warn');
1333
+ if (!actions.some((action) => action.type === COW_ACTIONS.CREATE)) {
1334
+ if (actions.length === 0) {
1335
+ return { executed: false, hadRotation: false };
1336
+ }
1337
+ }
1173
1338
  }
1174
1339
  const hasCreateActions = actions.some((action) => action.type === COW_ACTIONS.CREATE);
1175
1340
  if (hasCreateActions && bot.manager?._recoveryExhaustedAt) {
@@ -1549,6 +1714,12 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult) {
1549
1714
  }))
1550
1715
  };
1551
1716
  }
1717
+ // Pre-apply rotation state transitions to the working grid so the
1718
+ // COW commit is truly atomic for structural changes (source → VIRTUAL,
1719
+ // dest → ACTIVE with orderId). Remaining post-commit patches in
1720
+ // processBatchResults are limited to rawOnChain metadata enrichment
1721
+ // that depends on broadcast result data.
1722
+ applyRotationTransitionsToWorkingGrid(bot, workingGrid, executedContexts);
1552
1723
  bot.manager.logger.log('[COW] Blockchain success - committing working grid to master', 'info');
1553
1724
  await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, workingBoundary, { skipRecalc: true });
1554
1725
  const batchResult = await processBatchResults(bot, result, executedContexts);
@@ -1603,6 +1774,54 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult) {
1603
1774
  bot.manager.logger.log(`[COW] Non-atomic grouped execution detected (${err.groupsBroadcast}/${err.groupsTotal} groups broadcast). Local rollback cannot undo confirmed on-chain operations; next sync/reconcile will converge state.`, 'warn');
1604
1775
  }
1605
1776
  bot.manager.stopBroadcasting();
1777
+ // Chain polling: for uncertain broadcasts (not partial), try to confirm
1778
+ // CREATE operations on chain before clearing the working grid. If all
1779
+ // planned CREATEs are confirmed, the entire batch was accepted atomically
1780
+ // and we can commit the working grid directly, bypassing the expensive
1781
+ // reconciliation state machine. This handles the ~90% case where the
1782
+ // chain accepted the transaction but the response was lost.
1783
+ if (err instanceof BroadcastUncertainError && err.partialOnChainState !== true) {
1784
+ try {
1785
+ const confirmation = await pollChainForConfirmation(bot, opContexts);
1786
+ if (confirmation.allConfirmed) {
1787
+ // pollChainForConfirmation only verifies CREATE ops (see its doc).
1788
+ // If the batch had non-CREATE ops (UPDATEs/CANCELs), they are NOT
1789
+ // confirmed here — we assume atomic batch acceptance. Log the
1790
+ // composition so a misbehaving partial-broadcast (partialOnChainState
1791
+ // incorrectly false) leaves a forensic trace.
1792
+ const createCount = confirmation.confirmed.length;
1793
+ const totalOps = opContexts.length;
1794
+ if (createCount < totalOps) {
1795
+ bot.manager.logger.log(`[COW][UNCERTAIN] Chain polling confirmed ${createCount}/${totalOps} CREATEs on chain ` +
1796
+ `(${totalOps - createCount} non-CREATE ops assumed confirmed via atomic batch). ` +
1797
+ `Committing working grid.`, 'info');
1798
+ }
1799
+ else {
1800
+ bot.manager.logger.log(`[COW][UNCERTAIN] Chain polling confirmed all ${createCount} CREATE(s) on chain. Committing working grid directly.`, 'info');
1801
+ }
1802
+ applyRotationTransitionsToWorkingGrid(bot, workingGrid, opContexts);
1803
+ await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, workingBoundary, { skipRecalc: true });
1804
+ // Enrich master grid with chain-assigned order IDs and amounts
1805
+ try {
1806
+ const accountRef = bot.accountId || bot.account?.id || bot.account;
1807
+ const freshChain = await chainOrders.readOpenOrders(accountRef);
1808
+ if (freshChain.length > 0 && typeof bot.manager.syncFromOpenOrders === 'function') {
1809
+ await bot.manager.syncFromOpenOrders(freshChain, { skipAccounting: true });
1810
+ }
1811
+ }
1812
+ catch (syncErr) {
1813
+ bot.manager.logger.log(`[COW][UNCERTAIN] Chain sync after poll-confirmed commit failed: ${getErrorMessage(syncErr)}`, 'warn');
1814
+ }
1815
+ await bot.manager.persistGrid();
1816
+ bot.manager._clearWorkingGridRef();
1817
+ clearPendingBroadcasts(bot.manager?._pendingBroadcasts);
1818
+ return { executed: true, hadRotation: false, uncertainResolved: true };
1819
+ }
1820
+ }
1821
+ catch (pollErr) {
1822
+ bot.manager.logger.log(`[COW][UNCERTAIN] Chain polling threw unexpectedly: ${getErrorMessage(pollErr)}. Falling back to reconciliation.`, 'error');
1823
+ }
1824
+ }
1606
1825
  bot.manager._clearWorkingGridRef();
1607
1826
  if (err instanceof BroadcastUncertainError) {
1608
1827
  return await reconcileAfterUncertainBroadcast(bot, err, opContexts);
@@ -1839,6 +2058,8 @@ module.exports = {
1839
2058
  buildActionsFromPlan,
1840
2059
  buildCowResultFromPlan,
1841
2060
  restoreSkippedUpdateSlotsInWorkingGrid,
2061
+ applyRotationTransitionsToWorkingGrid,
2062
+ pollChainForConfirmation,
1842
2063
  updateOrdersOnChainBatchCOW,
1843
2064
  processBatchResults,
1844
2065
  };