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.
- package/README.md +1 -0
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +29 -1
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/constants.d.ts +1 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +5 -0
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +30 -0
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +228 -7
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +31 -25
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/launcher/credential_daemon.d.ts.map +1 -1
- package/dist/modules/launcher/credential_daemon.js +3 -4
- package/dist/modules/launcher/credential_daemon.js.map +1 -1
- package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/market_adapter_runtime.js +2 -4
- package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
- package/dist/modules/launcher/market_adapter_watchdog.d.ts.map +1 -1
- package/dist/modules/launcher/market_adapter_watchdog.js +4 -6
- package/dist/modules/launcher/market_adapter_watchdog.js.map +1 -1
- package/dist/modules/logger.js +0 -1
- package/dist/modules/logger.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +2 -2
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +3 -6
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/grid.d.ts +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +83 -69
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts +3 -9
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +80 -48
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +2 -8
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts +38 -7
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +89 -130
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.js +0 -1
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +1 -2
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +26 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +107 -54
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +10 -20
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +10 -4
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +47 -28
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/timeout.d.ts +25 -0
- package/dist/modules/order/utils/timeout.d.ts.map +1 -0
- package/dist/modules/order/utils/timeout.js +49 -0
- package/dist/modules/order/utils/timeout.js.map +1 -0
- package/dist/modules/order/utils/validate.d.ts +8 -1
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +33 -8
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts +6 -0
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -0
- package/dist/modules/order/utils/withPoolRef.js +129 -0
- package/dist/modules/order/utils/withPoolRef.js.map +1 -0
- package/dist/modules/types.d.ts +1 -0
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +2 -4
- package/dist/unlock.js.map +1 -1
- 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
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
};
|