dexbot 1.4.3 → 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 (42) hide show
  1. package/dist/modules/dexbot_cow_runtime.d.ts +30 -0
  2. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  3. package/dist/modules/dexbot_cow_runtime.js +192 -0
  4. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  5. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  6. package/dist/modules/dexbot_maintenance_runtime.js +31 -25
  7. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  8. package/dist/modules/order/accounting.d.ts +2 -2
  9. package/dist/modules/order/accounting.d.ts.map +1 -1
  10. package/dist/modules/order/accounting.js +3 -6
  11. package/dist/modules/order/accounting.js.map +1 -1
  12. package/dist/modules/order/grid.d.ts +1 -1
  13. package/dist/modules/order/grid.d.ts.map +1 -1
  14. package/dist/modules/order/grid.js +34 -35
  15. package/dist/modules/order/grid.js.map +1 -1
  16. package/dist/modules/order/grid_reconcile.d.ts +3 -9
  17. package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
  18. package/dist/modules/order/grid_reconcile.js +80 -48
  19. package/dist/modules/order/grid_reconcile.js.map +1 -1
  20. package/dist/modules/order/manager.d.ts +38 -7
  21. package/dist/modules/order/manager.d.ts.map +1 -1
  22. package/dist/modules/order/manager.js +83 -121
  23. package/dist/modules/order/manager.js.map +1 -1
  24. package/dist/modules/order/strategy.js +0 -1
  25. package/dist/modules/order/strategy.js.map +1 -1
  26. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  27. package/dist/modules/order/sync_engine.js +1 -2
  28. package/dist/modules/order/sync_engine.js.map +1 -1
  29. package/dist/modules/order/utils/math.d.ts +26 -1
  30. package/dist/modules/order/utils/math.d.ts.map +1 -1
  31. package/dist/modules/order/utils/math.js +91 -53
  32. package/dist/modules/order/utils/math.js.map +1 -1
  33. package/dist/modules/order/utils/order.d.ts.map +1 -1
  34. package/dist/modules/order/utils/order.js +10 -20
  35. package/dist/modules/order/utils/order.js.map +1 -1
  36. package/dist/modules/order/utils/system.d.ts +8 -3
  37. package/dist/modules/order/utils/system.d.ts.map +1 -1
  38. package/dist/modules/order/utils/system.js +23 -62
  39. package/dist/modules/order/utils/system.js.map +1 -1
  40. package/dist/modules/order/utils/validate.js +1 -1
  41. package/dist/modules/order/utils/validate.js.map +1 -1
  42. package/package.json +1 -1
@@ -268,6 +268,34 @@ declare function buildCowResultFromPlan(bot: any, plan: any): {
268
268
  * @param {Set<string>} skippedSlotIds
269
269
  * @param {number} [skippedCount=0]
270
270
  */
271
+ /**
272
+ * Pre-apply rotation state transitions to the working grid before commit.
273
+ * This makes the COW commit truly atomic for structural changes — source slots
274
+ * are cleared to VIRTUAL and destination slots are activated with the inherited
275
+ * orderId before the working grid is committed to master, eliminating the need
276
+ * for post-commit structural patching in processBatchResults.
277
+ *
278
+ * Only slot-to-slot rotations (newGridId exists) need pre-application;
279
+ * in-place rotations already have their size/price changes in the working grid.
280
+ */
281
+ declare function applyRotationTransitionsToWorkingGrid(bot: any, workingGrid: any, executedContexts: any): void;
282
+ /**
283
+ * Poll the chain after an uncertain broadcast to check if CREATE operations
284
+ * were actually accepted. Uses fingerprint matching against readOpenOrders.
285
+ * Falls back to reconciliation if polling cannot confirm within retries.
286
+ *
287
+ * We specifically confirm CREATE operations because they leave a detectable
288
+ * footprint on the chain (new order with matching fingerprint). UPDATEs and
289
+ * CANCELs modify existing orders and cannot be reliably distinguished from
290
+ * "not yet visible" state by simple polling.
291
+ *
292
+ * @returns {{ allConfirmed: boolean, confirmed: Array, unconfirmed: Array }}
293
+ */
294
+ declare function pollChainForConfirmation(bot: any, opContexts: any, options?: any): Promise<{
295
+ allConfirmed: boolean;
296
+ confirmed: any[];
297
+ unconfirmed: any[];
298
+ }>;
271
299
  declare function restoreSkippedUpdateSlotsInWorkingGrid(bot: any, workingGrid: any, skippedSlotIds: any, skippedCount?: any): void;
272
300
  /**
273
301
  * COW broadcast: Execute blockchain operations and commit working grid on success.
@@ -315,6 +343,8 @@ declare const _default: {
315
343
  buildActionsFromPlan: typeof buildActionsFromPlan;
316
344
  buildCowResultFromPlan: typeof buildCowResultFromPlan;
317
345
  restoreSkippedUpdateSlotsInWorkingGrid: typeof restoreSkippedUpdateSlotsInWorkingGrid;
346
+ applyRotationTransitionsToWorkingGrid: typeof applyRotationTransitionsToWorkingGrid;
347
+ pollChainForConfirmation: typeof pollChainForConfirmation;
318
348
  updateOrdersOnChainBatchCOW: typeof updateOrdersOnChainBatchCOW;
319
349
  processBatchResults: typeof processBatchResults;
320
350
  };
@@ -1 +1 @@
1
- {"version":3,"file":"dexbot_cow_runtime.d.ts","sourceRoot":"","sources":["../../modules/dexbot_cow_runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dexbot_cow_runtime.d.ts","sourceRoot":"","sources":["../../modules/dexbot_cow_runtime.ts"],"names":[],"mappings":"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"}
@@ -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;
@@ -1578,6 +1714,12 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult) {
1578
1714
  }))
1579
1715
  };
1580
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);
1581
1723
  bot.manager.logger.log('[COW] Blockchain success - committing working grid to master', 'info');
1582
1724
  await bot.manager._commitWorkingGrid(workingGrid, workingIndexes, workingBoundary, { skipRecalc: true });
1583
1725
  const batchResult = await processBatchResults(bot, result, executedContexts);
@@ -1632,6 +1774,54 @@ async function updateOrdersOnChainBatchCOW(bot, cowResult) {
1632
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');
1633
1775
  }
1634
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
+ }
1635
1825
  bot.manager._clearWorkingGridRef();
1636
1826
  if (err instanceof BroadcastUncertainError) {
1637
1827
  return await reconcileAfterUncertainBroadcast(bot, err, opContexts);
@@ -1868,6 +2058,8 @@ module.exports = {
1868
2058
  buildActionsFromPlan,
1869
2059
  buildCowResultFromPlan,
1870
2060
  restoreSkippedUpdateSlotsInWorkingGrid,
2061
+ applyRotationTransitionsToWorkingGrid,
2062
+ pollChainForConfirmation,
1871
2063
  updateOrdersOnChainBatchCOW,
1872
2064
  processBatchResults,
1873
2065
  };