dexbot 1.4.23 → 1.4.25
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/CHANGELOG.md +47 -0
- package/README.md +1 -0
- package/analysis/README.md +49 -0
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/grid_correction_check.ts +896 -0
- package/analysis/trend_detection/package.json +1 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/grid_correction_check.d.ts +90 -0
- package/dist/analysis/grid_correction_check.d.ts.map +1 -0
- package/dist/analysis/grid_correction_check.js +861 -0
- package/dist/analysis/grid_correction_check.js.map +1 -0
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +62 -6
- package/dist/dexbot.js.map +1 -1
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +53 -5
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/account_orders.d.ts +7 -10
- package/dist/modules/account_orders.d.ts.map +1 -1
- package/dist/modules/account_orders.js +26 -2
- package/dist/modules/account_orders.js.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.d.ts.map +1 -1
- package/dist/modules/bitshares-native/subscriptions.js +176 -21
- package/dist/modules/bitshares-native/subscriptions.js.map +1 -1
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +21 -2
- package/dist/modules/chain_orders.js.map +1 -1
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +27 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +15 -1
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +70 -6
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +31 -32
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +24 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +66 -12
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +22 -17
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +646 -128
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +11 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +108 -1
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +17 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +215 -26
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts +1 -0
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +58 -7
- package/dist/modules/dexbot_startup_runtime.js.map +1 -1
- package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
- package/dist/modules/dexbot_state_recovery.js +55 -3
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/graceful_shutdown.d.ts +12 -1
- package/dist/modules/graceful_shutdown.d.ts.map +1 -1
- package/dist/modules/graceful_shutdown.js +39 -1
- package/dist/modules/graceful_shutdown.js.map +1 -1
- package/dist/modules/key_store.d.ts.map +1 -1
- package/dist/modules/key_store.js +38 -1
- package/dist/modules/key_store.js.map +1 -1
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +20 -3
- 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 +288 -31
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts +4 -2
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +242 -222
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +11 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +132 -6
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +68 -14
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +241 -52
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/strategy.d.ts.map +1 -1
- package/dist/modules/order/strategy.js +25 -8
- 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 +261 -166
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +47 -5
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +175 -11
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +16 -15
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +199 -32
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/slot.d.ts +8 -0
- package/dist/modules/order/utils/slot.d.ts.map +1 -0
- package/dist/modules/order/utils/slot.js +16 -0
- package/dist/modules/order/utils/slot.js.map +1 -0
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +3 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts +5 -8
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +64 -82
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +5 -0
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +21 -0
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/paths.d.ts +4 -2
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +79 -24
- package/dist/modules/paths.js.map +1 -1
- package/dist/scripts/analyze-orders.d.ts.map +1 -1
- package/dist/scripts/analyze-orders.js +23 -2
- package/dist/scripts/analyze-orders.js.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts +2 -5
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +1 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +83 -1
- package/dist/scripts/generate_market_adapter_whitelist.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +15 -15
- package/docs/EVOLUTION.md +10 -8
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_RECALCULATION.md +6 -0
- package/docs/README.md +2 -2
- package/market_adapter/README.md +10 -0
- package/package.json +2 -1
- package/scripts/README.md +4 -0
|
@@ -3,11 +3,10 @@ const require = createRequire(import.meta.url);
|
|
|
3
3
|
import { _countActiveOnGrid, _cancelChainOrder, _recoverStartupSyncFailure, _refreshStartupUpdatePlans, _executeStartupUpdateBatch, _executeStartupSequentialUpdateFallback, _executePlannedStartupCreates, _reconcileStartupSide, } from './grid_reconcile_internal.js';
|
|
4
4
|
import { ORDER_TYPES, ORDER_STATES, TIMING } from '../constants.js';
|
|
5
5
|
import { readOpenOrdersGuarded } from '../chain_orders.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getAssetFeesSafe, priceSlotEqual } from './utils/math.js';
|
|
7
7
|
import { isOrderPlaced, parseChainOrder, isOrderOnChain, chainOrderMatchesSlot, duplicateOrphanLogInfo, } from './utils/order.js';
|
|
8
8
|
import * as Format from './format.js';
|
|
9
9
|
import { getErrorMessage } from '../utils/errors.js';
|
|
10
|
-
const SUSPECTED_DUPLICATE_TOLERANCE_MULTIPLIER = 5;
|
|
11
10
|
/**
|
|
12
11
|
* Returns { resumed: boolean, matchedCount: number }.
|
|
13
12
|
* @param {Object} params - Destructured parameters
|
|
@@ -18,7 +17,7 @@ const SUSPECTED_DUPLICATE_TOLERANCE_MULTIPLIER = 5;
|
|
|
18
17
|
* @param {Function} params.storeGrid - Grid persistence callback
|
|
19
18
|
* @returns {Promise<{resumed: boolean, matchedCount: number}>}
|
|
20
19
|
*/
|
|
21
|
-
export async function attemptResumePersistedGridByPriceMatch({ manager, persistedGrid, chainOpenOrders, logger, storeGrid, boundaryIdx = null, }) {
|
|
20
|
+
export async function attemptResumePersistedGridByPriceMatch({ manager, persistedGrid, chainOpenOrders, logger, storeGrid, boundaryIdx = null, genesis = null, }) {
|
|
22
21
|
if (!Array.isArray(persistedGrid) || persistedGrid.length === 0)
|
|
23
22
|
return { resumed: false, matchedCount: 0 };
|
|
24
23
|
if (!Array.isArray(chainOpenOrders) || chainOpenOrders.length === 0)
|
|
@@ -28,7 +27,9 @@ export async function attemptResumePersistedGridByPriceMatch({ manager, persiste
|
|
|
28
27
|
try {
|
|
29
28
|
logger && logger.log && logger.log('No matching active order IDs found. Attempting to match by price...', 'info');
|
|
30
29
|
const { loadGrid } = require('./grid');
|
|
31
|
-
|
|
30
|
+
// Prefer explicit genesis if caller wired it, else fall back to manager._genesis
|
|
31
|
+
const genesisArg = genesis ?? manager?._genesis ?? null;
|
|
32
|
+
await loadGrid(manager, persistedGrid, boundaryIdx, genesisArg);
|
|
32
33
|
await manager.synchronizeWithChain(chainOpenOrders, 'readOpenOrders');
|
|
33
34
|
const matchedOrderIds = new Set(Array.from(manager.orders.values())
|
|
34
35
|
.filter((o) => o && isOrderOnChain(o))
|
|
@@ -66,7 +67,7 @@ export async function attemptResumePersistedGridByPriceMatch({ manager, persiste
|
|
|
66
67
|
* @param {Function} [params.attemptResumeFn=attemptResumePersistedGridByPriceMatch] - Resume function
|
|
67
68
|
* @returns {Promise<Object>} { shouldRegenerate, hasActiveMatch, resumedByPrice, matchedCount }
|
|
68
69
|
*/
|
|
69
|
-
export async function decideStartupGridAction({ persistedGrid, chainOpenOrders, manager, logger, storeGrid, boundaryIdx = null, attemptResumeFn = attemptResumePersistedGridByPriceMatch, }) {
|
|
70
|
+
export async function decideStartupGridAction({ persistedGrid, chainOpenOrders, manager, logger, storeGrid, boundaryIdx = null, genesis = null, attemptResumeFn = attemptResumePersistedGridByPriceMatch, }) {
|
|
70
71
|
const persisted = Array.isArray(persistedGrid) ? persistedGrid : [];
|
|
71
72
|
const chain = Array.isArray(chainOpenOrders) ? chainOpenOrders : [];
|
|
72
73
|
if (persisted.length === 0) {
|
|
@@ -78,7 +79,7 @@ export async function decideStartupGridAction({ persistedGrid, chainOpenOrders,
|
|
|
78
79
|
return { shouldRegenerate: false, hasActiveMatch: true, resumedByPrice: false, matchedCount: 0 };
|
|
79
80
|
}
|
|
80
81
|
if (chain.length > 0) {
|
|
81
|
-
const resume = await attemptResumeFn({ manager, persistedGrid: persisted, chainOpenOrders: chain, logger, storeGrid, boundaryIdx });
|
|
82
|
+
const resume = await attemptResumeFn({ manager, persistedGrid: persisted, chainOpenOrders: chain, logger, storeGrid, boundaryIdx, genesis });
|
|
82
83
|
return { shouldRegenerate: !resume.resumed, hasActiveMatch: false, resumedByPrice: !!resume.resumed, matchedCount: resume.matchedCount || 0 };
|
|
83
84
|
}
|
|
84
85
|
return { shouldRegenerate: true, hasActiveMatch: false, resumedByPrice: false, matchedCount: 0 };
|
|
@@ -185,6 +186,12 @@ export async function reconcileGridOrders({ manager, config, account, privateKey
|
|
|
185
186
|
const plannedCancels = [];
|
|
186
187
|
const cancelledDuplicateIds = new Set();
|
|
187
188
|
const activeGridOrders = Array.from(manager.orders.values()).filter((o) => o && o.orderId && isOrderPlaced(o));
|
|
189
|
+
// Update-first policy: unmatched chain orders are NEVER cancelled here.
|
|
190
|
+
// They flow into _reconcileStartupSide below, which price-updates them onto
|
|
191
|
+
// rail slots in a single batch (plannedUpdates), creates missing orders, and
|
|
192
|
+
// cancels only true surplus (chainCount > targetCount). Pre-emptive
|
|
193
|
+
// cancellation would turn one batched update into per-order cancel+create
|
|
194
|
+
// churn and strip the bot's own live orders during a reset.
|
|
188
195
|
for (const u of unmatchedParsed) {
|
|
189
196
|
const p = u.parsed;
|
|
190
197
|
const desc = `Unmatched chain order: ${p.orderId} (${p.type === ORDER_TYPES.BUY ? 'BUY' : 'SELL'}), price=${Format.formatPrice6(p.price)}, size=${Format.formatSizeByOrderType(p.size ?? 0, p.type, manager.assets)}`;
|
|
@@ -192,18 +199,14 @@ export async function reconcileGridOrders({ manager, config, account, privateKey
|
|
|
192
199
|
for (const gridOrder of activeGridOrders) {
|
|
193
200
|
if (gridOrder.type !== p.type)
|
|
194
201
|
continue;
|
|
202
|
+
const precision = p.type === ORDER_TYPES.SELL ? manager.assets.assetA.precision : manager.assets.assetB.precision;
|
|
203
|
+
const isEqual = priceSlotEqual(p.price, gridOrder.price, precision);
|
|
195
204
|
const priceDiff = Math.abs(p.price - gridOrder.price);
|
|
196
|
-
const
|
|
197
|
-
const candidate = {
|
|
198
|
-
gridOrder,
|
|
199
|
-
priceDiff,
|
|
200
|
-
tolerance,
|
|
201
|
-
looseTolerance: tolerance * SUSPECTED_DUPLICATE_TOLERANCE_MULTIPLIER,
|
|
202
|
-
};
|
|
205
|
+
const candidate = { gridOrder, priceDiff, isEqual };
|
|
203
206
|
if (!nearest || priceDiff < nearest.priceDiff)
|
|
204
207
|
nearest = candidate;
|
|
205
208
|
}
|
|
206
|
-
if (nearest && nearest.
|
|
209
|
+
if (nearest && nearest.isEqual) {
|
|
207
210
|
// The sync layer already owns this duplicate: PASS 2 queues a
|
|
208
211
|
// cancel-only correction, and startup runs it (correctAllPriceMismatches)
|
|
209
212
|
// before reconcile — leaving a stale snapshot where the order is already
|
|
@@ -226,8 +229,7 @@ export async function reconcileGridOrders({ manager, config, account, privateKey
|
|
|
226
229
|
const { level, suffix } = duplicateOrphanLogInfo(p.orderId);
|
|
227
230
|
logger?.log?.(`SUSPECTED DUPLICATE: ${desc} - nearest active grid ${nearest.gridOrder.id} ` +
|
|
228
231
|
`(orderId=${nearest.gridOrder.orderId}, price=${Format.formatPrice6(nearest.gridOrder.price)}, ` +
|
|
229
|
-
`diff=${Format.formatPrice6(nearest.priceDiff)},
|
|
230
|
-
`looseTolerance=${Format.formatPrice6(nearest.looseTolerance)})${suffix}`, level);
|
|
232
|
+
`diff=${Format.formatPrice6(nearest.priceDiff)}, slotEqual=${nearest.isEqual})${suffix}`, level);
|
|
231
233
|
// Queue duplicate for Phase 2 cancellation instead of executing under lock
|
|
232
234
|
cancelledDuplicateIds.add(p.orderId);
|
|
233
235
|
plannedCancels.push({
|
|
@@ -239,8 +241,7 @@ export async function reconcileGridOrders({ manager, config, account, privateKey
|
|
|
239
241
|
else if (nearest) {
|
|
240
242
|
logger?.log?.(`${desc}; nearest active same-side grid ${nearest.gridOrder.id} ` +
|
|
241
243
|
`(orderId=${nearest.gridOrder.orderId}, price=${Format.formatPrice6(nearest.gridOrder.price)}, ` +
|
|
242
|
-
`diff=${Format.formatPrice6(nearest.priceDiff)},
|
|
243
|
-
`looseTolerance=${Format.formatPrice6(nearest.looseTolerance)})`, 'warn');
|
|
244
|
+
`diff=${Format.formatPrice6(nearest.priceDiff)}, slotEqual=${nearest.isEqual})`, 'warn');
|
|
244
245
|
}
|
|
245
246
|
else {
|
|
246
247
|
logger?.log?.(`${desc}; no active same-side grid order exists`, 'warn');
|
|
@@ -290,240 +291,259 @@ export async function reconcileGridOrders({ manager, config, account, privateKey
|
|
|
290
291
|
// These are the heavy operations that would block all other grid operations
|
|
291
292
|
// if held under _gridLock. All lock acquisitions here follow the canonical
|
|
292
293
|
// hierarchy (fillProcessingLock → syncLock → gridLock → fundLock).
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
catch (cancelErr) {
|
|
313
|
-
logger?.log?.(`Startup: Failed to cancel queued order ${cancelPlan.chainOrderId}: ${getErrorMessage(cancelErr)}`, 'error');
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
if (!dryRun && plannedUpdates.length > 0) {
|
|
318
|
-
let updatePlans = plannedUpdates;
|
|
319
|
-
const maxBatchAttempts = 3;
|
|
320
|
-
let batchCompleted = false;
|
|
321
|
-
if (supportsBatchUpdate) {
|
|
322
|
-
for (let attempt = 1; attempt <= maxBatchAttempts; attempt++) {
|
|
294
|
+
// P0 placement mutex: the whole of Phase 2 is broadcast I/O plus master-grid
|
|
295
|
+
// mutations, and it is NOT under _gridLock. Without the broadcasting gate a
|
|
296
|
+
// fill-driven COW rebalance can plan and broadcast the SAME slots in parallel
|
|
297
|
+
// with this loop: the commit is then refused ("master mutation during
|
|
298
|
+
// broadcasting"), both order generations land on chain, and the loser becomes
|
|
299
|
+
// a duplicate-price orphan backlog that blocks placements until drained.
|
|
300
|
+
// startBroadcasting() defers that rebalance —
|
|
301
|
+
// manager.performSafeRebalance waits on the flag and the fill consumer gates
|
|
302
|
+
// on it. The flag is refcounted and has a 120s stale-clear watchdog.
|
|
303
|
+
if (typeof manager.startBroadcasting === 'function')
|
|
304
|
+
manager.startBroadcasting();
|
|
305
|
+
try {
|
|
306
|
+
const { plannedCreates, plannedUpdates, plannedCancels, chainSellCount, chainBuyCount } = phase1Result;
|
|
307
|
+
// Execute planned cancellations (duplicates, edge, excess) outside lock.
|
|
308
|
+
// Each _cancelChainOrder call acquires _gridLock internally via synchronizeWithChain.
|
|
309
|
+
if (!dryRun && plannedCancels.length > 0) {
|
|
310
|
+
logger?.log?.(`Startup: Executing ${plannedCancels.length} queued cancellations (Phase 2)`, 'info');
|
|
311
|
+
for (const cancelPlan of plannedCancels) {
|
|
323
312
|
try {
|
|
324
|
-
|
|
325
|
-
updatePlans,
|
|
313
|
+
await _cancelChainOrder({
|
|
326
314
|
chainOrders,
|
|
327
315
|
account,
|
|
328
316
|
privateKey,
|
|
329
317
|
manager,
|
|
318
|
+
chainOrderId: cancelPlan.chainOrderId,
|
|
330
319
|
dryRun,
|
|
320
|
+
chainOrderObj: cancelPlan.chainOrderObj,
|
|
321
|
+
releaseUntrackedFunds: cancelPlan.releaseUntrackedFunds,
|
|
331
322
|
});
|
|
332
|
-
|
|
333
|
-
|
|
323
|
+
logger?.log?.(`Startup: Cancelled queued order ${cancelPlan.chainOrderId} (Phase 2)`, 'info');
|
|
324
|
+
}
|
|
325
|
+
catch (cancelErr) {
|
|
326
|
+
logger?.log?.(`Startup: Failed to cancel queued order ${cancelPlan.chainOrderId}: ${getErrorMessage(cancelErr)}. ` +
|
|
327
|
+
`The order stays live: the relocation/create phases will skip any placement ` +
|
|
328
|
+
`that would cross it (STARTUP-CROSS-GUARD) and re-align on the next cycle.`, 'error');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (!dryRun && plannedUpdates.length > 0) {
|
|
333
|
+
let updatePlans = plannedUpdates;
|
|
334
|
+
const maxBatchAttempts = 3;
|
|
335
|
+
let batchCompleted = false;
|
|
336
|
+
if (supportsBatchUpdate) {
|
|
337
|
+
for (let attempt = 1; attempt <= maxBatchAttempts; attempt++) {
|
|
338
|
+
try {
|
|
339
|
+
const batchResult = await _executeStartupUpdateBatch({
|
|
340
|
+
updatePlans,
|
|
341
|
+
chainOrders,
|
|
342
|
+
account,
|
|
343
|
+
privateKey,
|
|
344
|
+
manager,
|
|
345
|
+
dryRun,
|
|
346
|
+
});
|
|
347
|
+
if (batchResult.executed) {
|
|
348
|
+
logger?.log?.(`Startup: Update batch complete (${batchResult.prepared} updated)`, 'info');
|
|
349
|
+
}
|
|
350
|
+
batchCompleted = true;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
logger?.log?.(`Startup: Update batch attempt ${attempt}/${maxBatchAttempts} failed: ${getErrorMessage(err)}`, 'error');
|
|
355
|
+
const refreshedChainOrders = await _recoverStartupSyncFailure({
|
|
356
|
+
chainOrders,
|
|
357
|
+
manager,
|
|
358
|
+
account,
|
|
359
|
+
logger,
|
|
360
|
+
triggerMessage: `Startup: Triggering recovery sync after update batch failure (attempt ${attempt}/${maxBatchAttempts})`,
|
|
361
|
+
source: 'startupReconcileUpdateBatchFailure',
|
|
362
|
+
});
|
|
363
|
+
updatePlans = _refreshStartupUpdatePlans(updatePlans, refreshedChainOrders);
|
|
364
|
+
if (updatePlans.length === 0) {
|
|
365
|
+
logger?.log?.('Startup: No update plans remain after recovery sync; skipping further update attempts', 'warn');
|
|
366
|
+
batchCompleted = true;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
if (attempt >= maxBatchAttempts) {
|
|
370
|
+
logger?.log?.('Startup: Update batch retries exhausted; switching to sequential fallback', 'warn');
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
334
373
|
}
|
|
335
|
-
batchCompleted = true;
|
|
336
|
-
break;
|
|
337
374
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
logger?.log?.('Startup: Batch update helpers unavailable; using sequential fallback updates', 'warn');
|
|
378
|
+
}
|
|
379
|
+
if (!batchCompleted && updatePlans.length > 0) {
|
|
380
|
+
try {
|
|
381
|
+
const fallbackResult = await _executeStartupSequentialUpdateFallback({
|
|
382
|
+
updatePlans,
|
|
341
383
|
chainOrders,
|
|
342
|
-
manager,
|
|
343
384
|
account,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
privateKey,
|
|
386
|
+
manager,
|
|
387
|
+
dryRun,
|
|
347
388
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
logger?.log?.('Startup: No update plans remain after recovery sync; skipping further update attempts', 'warn');
|
|
351
|
-
batchCompleted = true;
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
if (attempt >= maxBatchAttempts) {
|
|
355
|
-
logger?.log?.('Startup: Update batch retries exhausted; switching to sequential fallback', 'warn');
|
|
356
|
-
break;
|
|
389
|
+
if (fallbackResult.executed > 0 || fallbackResult.skipped > 0 || fallbackResult.failed > 0) {
|
|
390
|
+
logger?.log?.(`Startup: Sequential fallback complete (updated=${fallbackResult.executed}, skipped=${fallbackResult.skipped}, failed=${fallbackResult.failed})`, fallbackResult.failed > 0 ? 'warn' : 'info');
|
|
357
391
|
}
|
|
358
392
|
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
logger?.log?.(`Startup: Sequential fallback failed unexpectedly: ${getErrorMessage(err)}`, 'error');
|
|
395
|
+
}
|
|
359
396
|
}
|
|
360
397
|
}
|
|
361
|
-
|
|
362
|
-
|
|
398
|
+
const phase2CreatedOrderIds = new Set();
|
|
399
|
+
if (!dryRun && plannedCreates.length > 0) {
|
|
400
|
+
const createdIds = await _executePlannedStartupCreates({
|
|
401
|
+
createPlans: plannedCreates,
|
|
402
|
+
chainOrders,
|
|
403
|
+
account,
|
|
404
|
+
privateKey,
|
|
405
|
+
manager,
|
|
406
|
+
dryRun,
|
|
407
|
+
});
|
|
408
|
+
for (const id of createdIds) {
|
|
409
|
+
phase2CreatedOrderIds.add(id);
|
|
410
|
+
}
|
|
363
411
|
}
|
|
364
|
-
|
|
412
|
+
let finalChainSellCount = chainSellCount;
|
|
413
|
+
let finalChainBuyCount = chainBuyCount;
|
|
414
|
+
if (!dryRun) {
|
|
365
415
|
try {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
416
|
+
// Truncated-read guard: get_full_accounts caps the limit_orders
|
|
417
|
+
// window and fresh orders (exactly the Phase-2 creates) sort last
|
|
418
|
+
// and are the first entries omitted. The adoption loop and the
|
|
419
|
+
// surplus-cancel counts would silently operate on a partial
|
|
420
|
+
// snapshot — defer to the sync loop's targeted drift detection and
|
|
421
|
+
// keep the pre-Phase-2 counts for the summary log.
|
|
422
|
+
const freshOpenOrders = await readOpenOrdersGuarded(chainOrders, account, {
|
|
423
|
+
log: (message, level) => logger?.log?.(message, level),
|
|
424
|
+
label: 'STARTUP',
|
|
425
|
+
detail: 'Post-Phase-2 chain read',
|
|
373
426
|
});
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
const phase2CreatedOrderIds = new Set();
|
|
384
|
-
if (!dryRun && plannedCreates.length > 0) {
|
|
385
|
-
const createdIds = await _executePlannedStartupCreates({
|
|
386
|
-
createPlans: plannedCreates,
|
|
387
|
-
chainOrders,
|
|
388
|
-
account,
|
|
389
|
-
privateKey,
|
|
390
|
-
manager,
|
|
391
|
-
dryRun,
|
|
392
|
-
});
|
|
393
|
-
for (const id of createdIds) {
|
|
394
|
-
phase2CreatedOrderIds.add(id);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
let finalChainSellCount = chainSellCount;
|
|
398
|
-
let finalChainBuyCount = chainBuyCount;
|
|
399
|
-
if (!dryRun) {
|
|
400
|
-
try {
|
|
401
|
-
// Truncated-read guard: get_full_accounts caps the limit_orders
|
|
402
|
-
// window and fresh orders (exactly the Phase-2 creates) sort last
|
|
403
|
-
// and are the first entries omitted. The adoption loop and the
|
|
404
|
-
// surplus-cancel counts would silently operate on a partial
|
|
405
|
-
// snapshot — defer to the sync loop's targeted drift detection and
|
|
406
|
-
// keep the pre-Phase-2 counts for the summary log.
|
|
407
|
-
const freshOpenOrders = await readOpenOrdersGuarded(chainOrders, account, {
|
|
408
|
-
log: (message, level) => logger?.log?.(message, level),
|
|
409
|
-
label: 'STARTUP',
|
|
410
|
-
detail: 'Post-Phase-2 chain read',
|
|
411
|
-
});
|
|
412
|
-
if (freshOpenOrders !== null) {
|
|
413
|
-
const freshParsed = (Array.isArray(freshOpenOrders) ? freshOpenOrders : [])
|
|
414
|
-
.map((co) => ({ chain: co, parsed: parseChainOrder(co, manager.assets) }))
|
|
415
|
-
.filter((x) => x.parsed);
|
|
416
|
-
const gridOrderIds = new Set();
|
|
417
|
-
for (const order of manager.orders.values()) {
|
|
418
|
-
if (order && order.orderId)
|
|
419
|
-
gridOrderIds.add(order.orderId);
|
|
420
|
-
}
|
|
421
|
-
// Merge any chain order IDs Phase 2 successfully created on-chain
|
|
422
|
-
// (even if _applySync failed to register them in manager.orders).
|
|
423
|
-
// This prevents Phase 3 from cancelling legitimately created orders.
|
|
424
|
-
for (const id of phase2CreatedOrderIds) {
|
|
425
|
-
gridOrderIds.add(id);
|
|
426
|
-
}
|
|
427
|
-
// Adopt any Phase-2 uncertain-landed chain orders that never made
|
|
428
|
-
// it into grid slots (the group adoption sync only runs when the
|
|
429
|
-
// post-uncertain read returned orders). Targeted slot adoption
|
|
430
|
-
// only: match VIRTUAL/SPREAD slots without an orderId by
|
|
431
|
-
// type+price+size (within tolerance). Full syncFromOpenOrders is
|
|
432
|
-
// deliberately NOT used here — its pass-1 virtualizes ACTIVE slots
|
|
433
|
-
// missing from the snapshot, and a lagging read right after the
|
|
434
|
-
// Phase-2 broadcast would destroy the confirmed grid.
|
|
435
|
-
const btsFeeData = getAssetFeesSafe('BTS');
|
|
436
|
-
for (const entry of freshParsed) {
|
|
437
|
-
const co = entry.chain;
|
|
438
|
-
const parsed = entry.parsed;
|
|
439
|
-
if (!co?.id || !parsed)
|
|
440
|
-
continue;
|
|
441
|
-
if (gridOrderIds.has(co.id))
|
|
442
|
-
continue;
|
|
443
|
-
const candidate = Array.from(manager.orders.values()).find((o) => {
|
|
444
|
-
if (!o || o.orderId || o.state !== ORDER_STATES.VIRTUAL)
|
|
445
|
-
return false;
|
|
446
|
-
return chainOrderMatchesSlot(parsed, o, manager.assets);
|
|
447
|
-
});
|
|
448
|
-
if (!candidate)
|
|
449
|
-
continue;
|
|
450
|
-
try {
|
|
451
|
-
await manager._applySync({
|
|
452
|
-
gridOrderId: candidate.id,
|
|
453
|
-
chainOrderId: co.id,
|
|
454
|
-
isPartialPlacement: false,
|
|
455
|
-
expectedType: parsed.type,
|
|
456
|
-
fee: btsFeeData?.createFee || 0,
|
|
457
|
-
}, 'createOrder');
|
|
458
|
-
gridOrderIds.add(co.id);
|
|
459
|
-
phase2CreatedOrderIds.add(co.id);
|
|
460
|
-
logger?.log?.(`Startup: Adopted uncertain-landed chain order ${co.id} into slot ${candidate.id} (${parsed.type}, price=${parsed.price})`, 'warn');
|
|
427
|
+
if (freshOpenOrders !== null) {
|
|
428
|
+
const freshParsed = (Array.isArray(freshOpenOrders) ? freshOpenOrders : [])
|
|
429
|
+
.map((co) => ({ chain: co, parsed: parseChainOrder(co, manager.assets) }))
|
|
430
|
+
.filter((x) => x.parsed);
|
|
431
|
+
const gridOrderIds = new Set();
|
|
432
|
+
for (const order of manager.orders.values()) {
|
|
433
|
+
if (order && order.orderId)
|
|
434
|
+
gridOrderIds.add(order.orderId);
|
|
461
435
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// mirror the capturedId protection of _createOrderFromGrid.
|
|
468
|
-
// The next sync loop's orphan adoption registers it.
|
|
469
|
-
gridOrderIds.add(co.id);
|
|
436
|
+
// Merge any chain order IDs Phase 2 successfully created on-chain
|
|
437
|
+
// (even if _applySync failed to register them in manager.orders).
|
|
438
|
+
// This prevents Phase 3 from cancelling legitimately created orders.
|
|
439
|
+
for (const id of phase2CreatedOrderIds) {
|
|
440
|
+
gridOrderIds.add(id);
|
|
470
441
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
442
|
+
// Adopt any Phase-2 uncertain-landed chain orders that never made
|
|
443
|
+
// it into grid slots (the group adoption sync only runs when the
|
|
444
|
+
// post-uncertain read returned orders). Targeted slot adoption
|
|
445
|
+
// only: match VIRTUAL/SPREAD slots without an orderId by
|
|
446
|
+
// type+price+size (within tolerance). Full syncFromOpenOrders is
|
|
447
|
+
// deliberately NOT used here — its pass-1 virtualizes ACTIVE slots
|
|
448
|
+
// missing from the snapshot, and a lagging read right after the
|
|
449
|
+
// Phase-2 broadcast would destroy the confirmed grid.
|
|
450
|
+
const btsFeeData = getAssetFeesSafe('BTS');
|
|
451
|
+
for (const entry of freshParsed) {
|
|
452
|
+
const co = entry.chain;
|
|
453
|
+
const parsed = entry.parsed;
|
|
454
|
+
if (!co?.id || !parsed)
|
|
455
|
+
continue;
|
|
456
|
+
if (gridOrderIds.has(co.id))
|
|
457
|
+
continue;
|
|
458
|
+
const candidate = Array.from(manager.orders.values()).find((o) => {
|
|
459
|
+
if (!o || o.orderId || o.state !== ORDER_STATES.VIRTUAL)
|
|
460
|
+
return false;
|
|
461
|
+
return chainOrderMatchesSlot(parsed, o, manager.assets);
|
|
462
|
+
});
|
|
463
|
+
if (!candidate)
|
|
464
|
+
continue;
|
|
465
|
+
try {
|
|
466
|
+
await manager._applySync({
|
|
467
|
+
gridOrderId: candidate.id,
|
|
468
|
+
chainOrderId: co.id,
|
|
469
|
+
isPartialPlacement: false,
|
|
470
|
+
expectedType: parsed.type,
|
|
471
|
+
fee: btsFeeData?.createFee || 0,
|
|
472
|
+
}, 'createOrder');
|
|
473
|
+
gridOrderIds.add(co.id);
|
|
474
|
+
phase2CreatedOrderIds.add(co.id);
|
|
475
|
+
logger?.log?.(`Startup: Adopted uncertain-landed chain order ${co.id} into slot ${candidate.id} (${parsed.type}, price=${parsed.price})`, 'warn');
|
|
476
|
+
}
|
|
477
|
+
catch (adoptErr) {
|
|
478
|
+
logger?.log?.(`Startup: Failed to adopt landed order ${co.id} into slot ${candidate?.id}: ${getErrorMessage(adoptErr)}`, 'warn');
|
|
479
|
+
// The order IS on chain (Phase-2 uncertain-landed). Even
|
|
480
|
+
// though the slot registration failed, the ID must still
|
|
481
|
+
// be protected from the same pass's surplus-cancel —
|
|
482
|
+
// mirror the capturedId protection of _createOrderFromGrid.
|
|
483
|
+
// The next sync loop's orphan adoption registers it.
|
|
484
|
+
gridOrderIds.add(co.id);
|
|
488
485
|
}
|
|
489
486
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
cancelledSellCount++;
|
|
508
|
-
else
|
|
509
|
-
cancelledBuyCount++;
|
|
510
|
-
logger?.log?.(`Startup: Cancelled stale surplus ${sc.sideLabel} order ${sc.chainOrderObj.id}`, 'info');
|
|
487
|
+
const staleSurplusCancels = [];
|
|
488
|
+
for (const side of [ORDER_TYPES.SELL, ORDER_TYPES.BUY]) {
|
|
489
|
+
const targetCount = side === ORDER_TYPES.SELL ? targetSell : targetBuy;
|
|
490
|
+
let sideOrders = freshParsed.filter((x) => x.parsed.type === side);
|
|
491
|
+
const sideLabel = side === ORDER_TYPES.SELL ? 'SELL' : 'BUY';
|
|
492
|
+
if (sideOrders.length > targetCount) {
|
|
493
|
+
// Sort by chain ID for deterministic cancellation order.
|
|
494
|
+
sideOrders = sideOrders.sort((a, b) => (a.chain.id || '').localeCompare(b.chain.id || ''));
|
|
495
|
+
let cancelLimit = sideOrders.length - targetCount;
|
|
496
|
+
for (const so of sideOrders) {
|
|
497
|
+
if (cancelLimit <= 0)
|
|
498
|
+
break;
|
|
499
|
+
if (!gridOrderIds.has(so.chain.id)) {
|
|
500
|
+
staleSurplusCancels.push({ chainOrderObj: so.chain, sideLabel });
|
|
501
|
+
cancelLimit--;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
511
504
|
}
|
|
512
|
-
|
|
513
|
-
|
|
505
|
+
}
|
|
506
|
+
let cancelledSellCount = 0;
|
|
507
|
+
let cancelledBuyCount = 0;
|
|
508
|
+
if (staleSurplusCancels.length > 0) {
|
|
509
|
+
for (const sc of staleSurplusCancels) {
|
|
510
|
+
try {
|
|
511
|
+
await _cancelChainOrder({
|
|
512
|
+
chainOrders,
|
|
513
|
+
account,
|
|
514
|
+
privateKey,
|
|
515
|
+
manager,
|
|
516
|
+
chainOrderId: sc.chainOrderObj.id,
|
|
517
|
+
dryRun,
|
|
518
|
+
chainOrderObj: sc.chainOrderObj,
|
|
519
|
+
releaseUntrackedFunds: true,
|
|
520
|
+
});
|
|
521
|
+
if (sc.sideLabel === 'SELL')
|
|
522
|
+
cancelledSellCount++;
|
|
523
|
+
else
|
|
524
|
+
cancelledBuyCount++;
|
|
525
|
+
logger?.log?.(`Startup: Cancelled stale surplus ${sc.sideLabel} order ${sc.chainOrderObj.id}`, 'info');
|
|
526
|
+
}
|
|
527
|
+
catch (e) {
|
|
528
|
+
logger?.log?.(`Startup: Failed to cancel surplus ${sc.sideLabel} ${sc.chainOrderObj.id}: ${getErrorMessage(e)}`, 'warn');
|
|
529
|
+
}
|
|
514
530
|
}
|
|
515
531
|
}
|
|
532
|
+
finalChainSellCount = freshParsed.filter((x) => x.parsed.type === ORDER_TYPES.SELL).length - cancelledSellCount;
|
|
533
|
+
finalChainBuyCount = freshParsed.filter((x) => x.parsed.type === ORDER_TYPES.BUY).length - cancelledBuyCount;
|
|
516
534
|
}
|
|
517
|
-
|
|
518
|
-
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
logger?.log?.(`Startup: Failed to refresh final chain counts: ${getErrorMessage(err)}`, 'warn');
|
|
519
538
|
}
|
|
520
539
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
540
|
+
logger && logger.log && logger.log(`Startup reconcile complete: target(sell=${targetSell}, buy=${targetBuy}), chain(sell=${finalChainSellCount}, buy=${finalChainBuyCount}), ` +
|
|
541
|
+
`gridActive(sell=${_countActiveOnGrid(manager, ORDER_TYPES.SELL)}, buy=${_countActiveOnGrid(manager, ORDER_TYPES.BUY)})`, 'info');
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
if (typeof manager.stopBroadcasting === 'function')
|
|
545
|
+
manager.stopBroadcasting();
|
|
524
546
|
}
|
|
525
|
-
logger && logger.log && logger.log(`Startup reconcile complete: target(sell=${targetSell}, buy=${targetBuy}), chain(sell=${finalChainSellCount}, buy=${finalChainBuyCount}), ` +
|
|
526
|
-
`gridActive(sell=${_countActiveOnGrid(manager, ORDER_TYPES.SELL)}, buy=${_countActiveOnGrid(manager, ORDER_TYPES.BUY)})`, 'info');
|
|
527
547
|
return null;
|
|
528
548
|
}
|
|
529
549
|
//# sourceMappingURL=grid_reconcile.js.map
|