dexbot 1.4.7 → 1.4.8
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/credential-daemon.d.ts.map +1 -1
- package/dist/credential-daemon.js +416 -92
- package/dist/credential-daemon.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +61 -27
- package/dist/dexbot.js.map +1 -1
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +14 -24
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares-native/transport.d.ts.map +1 -1
- package/dist/modules/bitshares-native/transport.js +21 -0
- package/dist/modules/bitshares-native/transport.js.map +1 -1
- package/dist/modules/broadcast_failure.d.ts +21 -0
- package/dist/modules/broadcast_failure.d.ts.map +1 -0
- package/dist/modules/broadcast_failure.js +95 -0
- package/dist/modules/broadcast_failure.js.map +1 -0
- package/dist/modules/chain_keys.d.ts +2 -2
- package/dist/modules/chain_keys.d.ts.map +1 -1
- package/dist/modules/chain_keys.js +30 -56
- package/dist/modules/chain_keys.js.map +1 -1
- package/dist/modules/chain_orders.d.ts +79 -1
- package/dist/modules/chain_orders.d.ts.map +1 -1
- package/dist/modules/chain_orders.js +154 -10
- package/dist/modules/chain_orders.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 +21 -7
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/daemon_node_health.d.ts +33 -0
- package/dist/modules/daemon_node_health.d.ts.map +1 -0
- package/dist/modules/daemon_node_health.js +120 -0
- package/dist/modules/daemon_node_health.js.map +1 -0
- package/dist/modules/dexbot_class.d.ts +12 -3
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +19 -5
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +46 -1
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +754 -64
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_credential_client.d.ts +15 -0
- package/dist/modules/dexbot_credential_client.d.ts.map +1 -1
- package/dist/modules/dexbot_credential_client.js +114 -141
- package/dist/modules/dexbot_credential_client.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +45 -15
- package/dist/modules/dexbot_fill_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +5 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +237 -84
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_startup_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_startup_runtime.js +95 -33
- 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 +24 -2
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/key_store.d.ts.map +1 -1
- package/dist/modules/key_store.js +5 -4
- package/dist/modules/key_store.js.map +1 -1
- package/dist/modules/launcher/credential_daemon.d.ts.map +1 -1
- package/dist/modules/launcher/credential_daemon.js +15 -3
- package/dist/modules/launcher/credential_daemon.js.map +1 -1
- package/dist/modules/node_failure_ledger.d.ts +39 -0
- package/dist/modules/node_failure_ledger.d.ts.map +1 -0
- package/dist/modules/node_failure_ledger.js +88 -0
- package/dist/modules/node_failure_ledger.js.map +1 -0
- package/dist/modules/node_health_cache.d.ts +2 -1
- package/dist/modules/node_health_cache.d.ts.map +1 -1
- package/dist/modules/node_health_cache.js +25 -0
- package/dist/modules/node_health_cache.js.map +1 -1
- package/dist/modules/node_manager.d.ts +3 -0
- package/dist/modules/node_manager.d.ts.map +1 -1
- package/dist/modules/node_manager.js +41 -10
- package/dist/modules/node_manager.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +1 -1
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +219 -101
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +66 -14
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +130 -56
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +2 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +333 -54
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +97 -4
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +377 -92
- 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 +24 -1
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts +16 -3
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +329 -247
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +79 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +18 -0
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +44 -1
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +91 -5
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +5 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +7 -5
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
- package/dist/modules/order/utils/withPoolRef.js +39 -4
- package/dist/modules/order/utils/withPoolRef.js.map +1 -1
- package/dist/modules/socket_json_client.d.ts +14 -0
- package/dist/modules/socket_json_client.d.ts.map +1 -0
- package/dist/modules/socket_json_client.js +100 -0
- package/dist/modules/socket_json_client.js.map +1 -0
- package/package.json +2 -1
- package/scripts/clear-all.sh +4 -4
- package/scripts/clear-logs.sh +4 -4
|
@@ -719,7 +719,12 @@ class SyncEngine {
|
|
|
719
719
|
else {
|
|
720
720
|
const spreadOrder = (0, order_1.convertToSpreadPlaceholder)(gridOrder);
|
|
721
721
|
await mgr._applyOrderUpdate(spreadOrder, 'sync-pass1-filled', { skipAccounting: skipAccounting, fee: 0 });
|
|
722
|
-
|
|
722
|
+
// Push the filled order with its REAL side (chain order
|
|
723
|
+
// type), not the SPREAD placeholder: downstream fill
|
|
724
|
+
// processing (deriveTargetBoundary) derives boundary
|
|
725
|
+
// movement from fill.type, and a SPREAD type would
|
|
726
|
+
// silently drop the shift for this completed order.
|
|
727
|
+
filledOrders.push({ ...gridOrder, type: chainOrder.type });
|
|
723
728
|
updatedOrders.push(spreadOrder);
|
|
724
729
|
}
|
|
725
730
|
}
|
|
@@ -760,7 +765,7 @@ class SyncEngine {
|
|
|
760
765
|
// this open-orders sync.
|
|
761
766
|
if (mgr._committedOrderIds?.has(gridOrder.orderId)) {
|
|
762
767
|
const commitAge = Date.now() - (mgr._committedOrderIdsBuiltAt || 0);
|
|
763
|
-
const isGhost = gridOrder.
|
|
768
|
+
const isGhost = gridOrder.size <= 0 && gridOrder.state === constants_1.ORDER_STATES.PARTIAL;
|
|
764
769
|
if (!isGhost && commitAge < constants_1.TIMING.SYNC_LOCK_TIMEOUT_MS) {
|
|
765
770
|
continue;
|
|
766
771
|
}
|
|
@@ -771,7 +776,18 @@ class SyncEngine {
|
|
|
771
776
|
await mgr._applyOrderUpdate(spreadOrder, 'sync-cleanup-phantom', { skipAccounting: skipAccounting, fee: 0 });
|
|
772
777
|
// Only genuine disappearances (had orderId) count as fills.
|
|
773
778
|
if (hadOrderId) {
|
|
774
|
-
|
|
779
|
+
// A SPREAD slot can carry an on-chain order (spread-correction
|
|
780
|
+
// activation). Resolve the real side before pushing: the fill
|
|
781
|
+
// drives deriveTargetBoundary, which only shifts on BUY/SELL —
|
|
782
|
+
// a SPREAD-typed fill would silently drop the boundary crawl
|
|
783
|
+
// for this completed order. Same price-vs-startPrice convention
|
|
784
|
+
// as the funds check (manager.ts) since no chain order remains
|
|
785
|
+
// to resolve the side from.
|
|
786
|
+
let resolvedType = currentGridOrder.type;
|
|
787
|
+
if (resolvedType === constants_1.ORDER_TYPES.SPREAD) {
|
|
788
|
+
resolvedType = (0, order_1.resolveSpreadOrderSide)(currentGridOrder.price, mgr.config.startPrice);
|
|
789
|
+
}
|
|
790
|
+
filledOrders.push({ ...currentGridOrder, type: resolvedType });
|
|
775
791
|
}
|
|
776
792
|
}
|
|
777
793
|
}
|
|
@@ -1012,7 +1028,22 @@ class SyncEngine {
|
|
|
1012
1028
|
return null;
|
|
1013
1029
|
}
|
|
1014
1030
|
_computeFillContext(mgr, matchedGridOrder, paysAssetId, paysAmountRaw) {
|
|
1015
|
-
|
|
1031
|
+
// A SPREAD slot can carry an on-chain order (e.g. spread-correction
|
|
1032
|
+
// activation). Resolve the real side from the fill's pays asset so the
|
|
1033
|
+
// transition result is BUY/SELL — never SPREAD with an on-chain state,
|
|
1034
|
+
// which validateOrder rejects as fatal ILLEGAL_SPREAD_STATE. When the
|
|
1035
|
+
// pays asset matches neither side (malformed fill event), fall back to
|
|
1036
|
+
// the price-vs-startPrice convention so the type can never leak SPREAD
|
|
1037
|
+
// into a fill result (which would drop the boundary shift downstream).
|
|
1038
|
+
let orderType = matchedGridOrder.type;
|
|
1039
|
+
if (orderType === constants_1.ORDER_TYPES.SPREAD) {
|
|
1040
|
+
if (paysAssetId === mgr.assets.assetB.id)
|
|
1041
|
+
orderType = constants_1.ORDER_TYPES.BUY;
|
|
1042
|
+
else if (paysAssetId === mgr.assets.assetA.id)
|
|
1043
|
+
orderType = constants_1.ORDER_TYPES.SELL;
|
|
1044
|
+
else
|
|
1045
|
+
orderType = (0, order_1.resolveSpreadOrderSide)(matchedGridOrder.price, mgr.config.startPrice);
|
|
1046
|
+
}
|
|
1016
1047
|
const currentSize = toFiniteNumber(matchedGridOrder.size);
|
|
1017
1048
|
const precision = (orderType === constants_1.ORDER_TYPES.SELL) ? mgr.assets.assetA.precision : mgr.assets.assetB.precision;
|
|
1018
1049
|
let filledAmount = 0;
|
|
@@ -1047,39 +1078,45 @@ class SyncEngine {
|
|
|
1047
1078
|
}
|
|
1048
1079
|
const gridAlsoEmpty = currentSizeIntFromGrid <= 0;
|
|
1049
1080
|
let isEffectivelyFull = resolvedChainConfirmsEmpty || gridAlsoEmpty;
|
|
1050
|
-
let ghostOrderId;
|
|
1051
1081
|
if (!isEffectivelyFull) {
|
|
1052
1082
|
const otherPrecision = (orderType === constants_1.ORDER_TYPES.SELL) ? mgr.assets.assetB.precision : mgr.assets.assetA.precision;
|
|
1053
|
-
const
|
|
1054
|
-
const otherSize = (orderType === constants_1.ORDER_TYPES.SELL) ? newSize *
|
|
1083
|
+
const otherSidePrice = matchedGridOrder.price;
|
|
1084
|
+
const otherSize = (orderType === constants_1.ORDER_TYPES.SELL) ? newSize * otherSidePrice : newSize / otherSidePrice;
|
|
1055
1085
|
if ((0, math_1.floatToBlockchainInt)(otherSize, otherPrecision) <= 0) {
|
|
1056
|
-
|
|
1086
|
+
// A fill event is authoritative: the order WAS on-chain and this
|
|
1087
|
+
// fill consumed the tradeable remainder. A fill is a fill — we do
|
|
1088
|
+
// not need to preserve the orderId as a "ghost" PARTIAL to block
|
|
1089
|
+
// a duplicate CREATE. Any residual dust that remains on-chain
|
|
1090
|
+
// after rounding is cancelled by the normal reconciliation /
|
|
1091
|
+
// duplicate-price cancel path, not by stalling the grid. Treating
|
|
1092
|
+
// every sub-dust other-side fill as a real full fill lets rotation
|
|
1093
|
+
// immediately plan the opposite-side replacement, preserving the
|
|
1094
|
+
// grid invariant (each filled side produces a new order on the
|
|
1095
|
+
// other side). This replaces the legacy ghost mechanism whose
|
|
1096
|
+
// preserved orderIds caused instantly-filled replacement orders
|
|
1097
|
+
// to deadlock the COW create pipeline.
|
|
1098
|
+
mgr.logger.log(`[SYNC] Order ${matchedGridOrder.orderId} (slot ${matchedGridOrder.id}) other-side (${otherSize}) rounds to 0. ` +
|
|
1099
|
+
`Fill is authoritative: treating as full fill for rotation; residual dust left for reconciliation cancel.`, 'info');
|
|
1057
1100
|
isEffectivelyFull = true;
|
|
1058
|
-
ghostOrderId = matchedGridOrder.orderId;
|
|
1059
1101
|
}
|
|
1060
1102
|
}
|
|
1061
1103
|
let fullUpdate;
|
|
1062
|
-
let ghostUpdate;
|
|
1063
1104
|
let partialUpdate;
|
|
1064
1105
|
let filledOrderResult;
|
|
1065
1106
|
if (isEffectivelyFull) {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1075
|
-
else {
|
|
1076
|
-
fullUpdate = (0, order_1.convertToSpreadPlaceholder)(matchedGridOrder);
|
|
1077
|
-
}
|
|
1078
|
-
filledOrderResult = { ...matchedGridOrder, blockNum, historyId, isMaker };
|
|
1107
|
+
fullUpdate = (0, order_1.convertToSpreadPlaceholder)(matchedGridOrder);
|
|
1108
|
+
filledOrderResult = {
|
|
1109
|
+
...matchedGridOrder,
|
|
1110
|
+
type: orderType,
|
|
1111
|
+
blockNum,
|
|
1112
|
+
historyId,
|
|
1113
|
+
isMaker
|
|
1114
|
+
};
|
|
1079
1115
|
}
|
|
1080
1116
|
else {
|
|
1081
1117
|
filledOrderResult = {
|
|
1082
1118
|
...matchedGridOrder,
|
|
1119
|
+
type: orderType,
|
|
1083
1120
|
size: filledAmount,
|
|
1084
1121
|
isPartial: true,
|
|
1085
1122
|
blockNum,
|
|
@@ -1087,7 +1124,7 @@ class SyncEngine {
|
|
|
1087
1124
|
isMaker
|
|
1088
1125
|
};
|
|
1089
1126
|
const { btsFeeState, ...matchedWithoutDeferredFee } = matchedGridOrder;
|
|
1090
|
-
let updatedOrder = { ...matchedWithoutDeferredFee, state: constants_1.ORDER_STATES.PARTIAL };
|
|
1127
|
+
let updatedOrder = { ...matchedWithoutDeferredFee, type: orderType, state: constants_1.ORDER_STATES.PARTIAL };
|
|
1091
1128
|
if (updatedOrder.rawOnChain && updatedOrder.rawOnChain.for_sale !== undefined) {
|
|
1092
1129
|
const baselineForSale = (chainRefetched && Number.isFinite(effectiveRawForSale))
|
|
1093
1130
|
? effectiveRawForSale
|
|
@@ -1107,11 +1144,8 @@ class SyncEngine {
|
|
|
1107
1144
|
}
|
|
1108
1145
|
return {
|
|
1109
1146
|
isFull: isEffectivelyFull,
|
|
1110
|
-
isGhost: !!ghostOrderId,
|
|
1111
|
-
ghostOrderId,
|
|
1112
1147
|
filledOrder: filledOrderResult,
|
|
1113
1148
|
fullUpdate,
|
|
1114
|
-
ghostUpdate,
|
|
1115
1149
|
partialUpdate,
|
|
1116
1150
|
newSize
|
|
1117
1151
|
};
|
|
@@ -1145,108 +1179,124 @@ class SyncEngine {
|
|
|
1145
1179
|
mgr.logger.log(`[SYNC] Missing replay-safe fill key for order ${orderId} block ${blockNum}; deferring to open-orders sync`, 'warn');
|
|
1146
1180
|
return { filledOrders: [], updatedOrders: [], partialFill: false, requiresOpenOrdersSync: true };
|
|
1147
1181
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1182
|
+
// Raise the fill-batch in-flight guard BEFORE the accountTotals refresh
|
|
1183
|
+
// (mirrors syncFromFillHistoryBatch): the refresh returns post-fill
|
|
1184
|
+
// balances while the grid still holds the filled order as committed, so
|
|
1185
|
+
// a fund-invariant check in this window would fire a spurious CRITICAL.
|
|
1186
|
+
// The guard is lowered before the final consolidated recalc so the
|
|
1187
|
+
// settled (re-anchored) state is still verified.
|
|
1188
|
+
mgr._fillBatchInFlight = (mgr._fillBatchInFlight ?? 0) + 1;
|
|
1150
1189
|
try {
|
|
1151
|
-
mgr.
|
|
1190
|
+
const totalsGate = await mgr.refreshAccountTotalsIfStale();
|
|
1191
|
+
if (!totalsGate.ok) {
|
|
1192
|
+
mgr.logger.log(`[SYNC] Deferring fill ${historyId}: accountTotals refresh failed (stale snapshot). Will be re-processed on the next cycle.`, 'warn');
|
|
1193
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false, requiresOpenOrdersSync: false, deferred: true };
|
|
1194
|
+
}
|
|
1195
|
+
const orderIdsToLock = new Set([orderId]);
|
|
1196
|
+
mgr.lockOrders([...orderIdsToLock]);
|
|
1152
1197
|
try {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
mgr.
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
mgr.
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
const
|
|
1179
|
-
if (
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1198
|
+
mgr.pauseFundRecalc();
|
|
1199
|
+
try {
|
|
1200
|
+
const appliedAccounting = await mgr.accountant.processFillAccounting(fillOp, fillKey, { persistenceMode });
|
|
1201
|
+
if (!appliedAccounting) {
|
|
1202
|
+
mgr.logger.log(`[SYNC] Replay detected for fill ${fillKey}; skipping duplicate order mutation`, 'debug');
|
|
1203
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false };
|
|
1204
|
+
}
|
|
1205
|
+
const assetAPrecision = mgr.assets?.assetA?.precision;
|
|
1206
|
+
const assetBPrecision = mgr.assets?.assetB?.precision;
|
|
1207
|
+
if (assetAPrecision === undefined || assetBPrecision === undefined) {
|
|
1208
|
+
mgr.logger?.log?.('Error: manager.assets precision missing in syncFromFillHistory', 'error');
|
|
1209
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false };
|
|
1210
|
+
}
|
|
1211
|
+
const matchedGridOrder = this._findMatchingGridOrder(mgr, orderId);
|
|
1212
|
+
if (!matchedGridOrder) {
|
|
1213
|
+
mgr.logger.log(`[SYNC] Fill for order ${orderId} ignored: order not found in active grid.`, 'debug');
|
|
1214
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false };
|
|
1215
|
+
}
|
|
1216
|
+
const ctx = this._computeFillContext(mgr, matchedGridOrder, paysAssetId, paysAmountRaw);
|
|
1217
|
+
mgr.logger.log(`[SYNC] Order ${orderId} (${ctx.orderType}) currentSize=${ctx.currentSize}, filledAmount=${ctx.filledAmount}`, 'debug');
|
|
1218
|
+
let effectiveRawForSale = ctx.rawForSaleInt;
|
|
1219
|
+
let chainConfirmsEmpty = false;
|
|
1220
|
+
let chainRefetched = false;
|
|
1221
|
+
if (ctx.driftSignal) {
|
|
1222
|
+
try {
|
|
1223
|
+
const fresh = await chainOrders.readSingleOrder(orderId, 3000);
|
|
1224
|
+
if (fresh) {
|
|
1225
|
+
const freshForSale = toFiniteNumber(fresh.for_sale, undefined);
|
|
1226
|
+
if (Number.isFinite(freshForSale)) {
|
|
1227
|
+
effectiveRawForSale = freshForSale;
|
|
1228
|
+
chainRefetched = true;
|
|
1229
|
+
mgr.logger.log(`[SYNC] Drift detected for ${orderId} (cached=${ctx.rawForSaleInt} < grid=${ctx.currentSizeIntFromGrid}); ` +
|
|
1230
|
+
`refetched for_sale=${freshForSale}`, 'warn');
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
chainConfirmsEmpty = true;
|
|
1235
|
+
mgr.logger.log(`[SYNC] Drift refetch for ${orderId} returned null; chain confirms empty`, 'info');
|
|
1184
1236
|
}
|
|
1185
1237
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
mgr.logger.log(`[SYNC] Drift refetch for ${orderId} returned null; chain confirms empty`, 'info');
|
|
1238
|
+
catch (refetchErr) {
|
|
1239
|
+
mgr.logger.log(`[SYNC] Drift refetch for ${orderId} failed; falling back to cache: ${refetchErr?.message || refetchErr}`, 'warn');
|
|
1189
1240
|
}
|
|
1190
1241
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (result.isGhost) {
|
|
1214
|
-
mgr.logger.log(`[SYNC] Ghost full fill for order ${orderId} (slot ${matchedGridOrder.id}): preserving orderId ${result.ghostOrderId} as PARTIAL to block duplicate CREATE.`, 'info');
|
|
1215
|
-
const ghostOk = await mgr._updateOrder(result.ghostUpdate, 'handle-fill-ghost', { skipAccounting: false, fee: 0 });
|
|
1216
|
-
if (ghostOk === false) {
|
|
1217
|
-
mgr.logger.log(`[SYNC] Failed to apply ghost fill state for order ${orderId}; marking totals stale for next sync cycle`, 'warn');
|
|
1242
|
+
const result = await this._computeFillTransitionResult(mgr, {
|
|
1243
|
+
matchedGridOrder,
|
|
1244
|
+
orderType: ctx.orderType,
|
|
1245
|
+
precision: ctx.precision,
|
|
1246
|
+
filledAmount: ctx.filledAmount,
|
|
1247
|
+
filledAmountInt: (0, math_1.floatToBlockchainInt)(ctx.filledAmount, ctx.precision),
|
|
1248
|
+
currentSizeIntFromGrid: ctx.currentSizeIntFromGrid,
|
|
1249
|
+
rawForSaleInt: ctx.rawForSaleInt,
|
|
1250
|
+
chainRefetched,
|
|
1251
|
+
chainConfirmsEmpty,
|
|
1252
|
+
effectiveRawForSale,
|
|
1253
|
+
blockNum,
|
|
1254
|
+
historyId,
|
|
1255
|
+
isMaker
|
|
1256
|
+
});
|
|
1257
|
+
const filledOrders = [];
|
|
1258
|
+
const updatedOrders = [];
|
|
1259
|
+
if (result.isFull) {
|
|
1260
|
+
mgr.logger.log(`[SYNC] Full fill for order ${orderId} (slot ${matchedGridOrder.id}).`, 'info');
|
|
1261
|
+
const fullOk = await mgr._updateOrder(result.fullUpdate, 'handle-fill-full', { skipAccounting: false, fee: 0 });
|
|
1262
|
+
if (fullOk === false) {
|
|
1263
|
+
mgr.logger.log(`[SYNC] Failed to convert filled order ${orderId} to spread placeholder; marking totals stale for next sync cycle`, 'warn');
|
|
1218
1264
|
mgr.accountTotalsStale = true;
|
|
1219
1265
|
}
|
|
1220
1266
|
filledOrders.push(result.filledOrder);
|
|
1221
|
-
return { filledOrders, updatedOrders, partialFill: false };
|
|
1222
1267
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1268
|
+
else {
|
|
1269
|
+
mgr.logger.log(`[SYNC] Partial fill for order ${orderId} (slot ${matchedGridOrder.id}): newSize=${result.newSize}`, 'info');
|
|
1270
|
+
const partialOk = await mgr._updateOrder(result.partialUpdate, 'handle-fill-partial', { skipAccounting: false, fee: 0 });
|
|
1271
|
+
if (partialOk === false) {
|
|
1272
|
+
mgr.logger.log(`[SYNC] Failed to update partially filled order ${orderId}; marking totals stale for next sync cycle`, 'warn');
|
|
1273
|
+
mgr.accountTotalsStale = true;
|
|
1274
|
+
}
|
|
1275
|
+
updatedOrders.push(result.partialUpdate);
|
|
1276
|
+
filledOrders.push(result.filledOrder);
|
|
1228
1277
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1278
|
+
// Re-anchor accountTotals to authoritative post-fill chain truth
|
|
1279
|
+
// after the fill's optimistic accounting + grid update (see
|
|
1280
|
+
// syncFromFillHistoryBatch for rationale).
|
|
1281
|
+
await mgr.reanchorAccountTotals('fill-sync');
|
|
1282
|
+
return { filledOrders, updatedOrders, partialFill: result.isFull ? false : true };
|
|
1231
1283
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
mgr.accountTotalsStale = true;
|
|
1238
|
-
}
|
|
1239
|
-
updatedOrders.push(result.partialUpdate);
|
|
1240
|
-
filledOrders.push(result.filledOrder);
|
|
1241
|
-
return { filledOrders, updatedOrders, partialFill: true };
|
|
1284
|
+
finally {
|
|
1285
|
+
// Lower the guard before the final consolidated recalc so the
|
|
1286
|
+
// settled (re-anchored) state is still verified.
|
|
1287
|
+
mgr._fillBatchInFlight = Math.max(0, (mgr._fillBatchInFlight ?? 0) - 1);
|
|
1288
|
+
await mgr.resumeFundRecalc();
|
|
1242
1289
|
}
|
|
1243
1290
|
}
|
|
1244
1291
|
finally {
|
|
1245
|
-
|
|
1292
|
+
mgr.unlockOrders([...orderIdsToLock]);
|
|
1246
1293
|
}
|
|
1247
1294
|
}
|
|
1248
1295
|
finally {
|
|
1249
|
-
|
|
1296
|
+
// Safety net: ensure the guard never leaks across cycles on early
|
|
1297
|
+
// returns (deferral) or exceptions in the refresh/lock region.
|
|
1298
|
+
if ((mgr._fillBatchInFlight ?? 0) > 0)
|
|
1299
|
+
mgr._fillBatchInFlight--;
|
|
1250
1300
|
}
|
|
1251
1301
|
}
|
|
1252
1302
|
/**
|
|
@@ -1299,172 +1349,197 @@ class SyncEngine {
|
|
|
1299
1349
|
}
|
|
1300
1350
|
// Phase 2: Lock all unique order IDs once
|
|
1301
1351
|
const allOrderIds = [...new Set(fillEntries.map(e => e.orderId))];
|
|
1302
|
-
|
|
1352
|
+
// Raise the fill-batch in-flight guard BEFORE the accountTotals refresh.
|
|
1353
|
+
// The refresh fetches post-fill balances, but the grid still holds the
|
|
1354
|
+
// filled orders as committed until the batch's grid mutation (Phase 7),
|
|
1355
|
+
// so any fund-invariant check in this window would fire a spurious
|
|
1356
|
+
// CRITICAL with diff == the fills' size. The guard is lowered right
|
|
1357
|
+
// before the final consolidated recalc so the settled state is still
|
|
1358
|
+
// verified.
|
|
1359
|
+
mgr._fillBatchInFlight = (mgr._fillBatchInFlight ?? 0) + 1;
|
|
1303
1360
|
try {
|
|
1304
|
-
mgr.
|
|
1361
|
+
const totalsGate = await mgr.refreshAccountTotalsIfStale();
|
|
1362
|
+
if (!totalsGate.ok) {
|
|
1363
|
+
mgr.logger.log('[SYNC] Deferring fill batch: accountTotals refresh failed (stale snapshot). Fills will be re-processed on the next cycle.', 'warn');
|
|
1364
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false, requiresOpenOrdersSync: false, deferred: true };
|
|
1365
|
+
}
|
|
1366
|
+
mgr.lockOrders(allOrderIds);
|
|
1305
1367
|
try {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
mgr.
|
|
1368
|
+
mgr.pauseFundRecalc();
|
|
1369
|
+
try {
|
|
1370
|
+
// Phase 3: Process accounting for each fill
|
|
1371
|
+
const validEntries = [];
|
|
1372
|
+
for (const entry of fillEntries) {
|
|
1373
|
+
try {
|
|
1374
|
+
const appliedAccounting = await mgr.accountant.processFillAccounting(entry.fillOp, entry.fillKey, { persistenceMode });
|
|
1375
|
+
if (!appliedAccounting) {
|
|
1376
|
+
mgr.logger.log(`[SYNC] Replay detected for fill ${entry.fillKey}; skipping duplicate order mutation`, 'debug');
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
validEntries.push(entry);
|
|
1380
|
+
}
|
|
1381
|
+
catch (acctErr) {
|
|
1382
|
+
mgr.logger.log(`[SYNC] Accounting error for fill ${entry.fillKey}: ${(0, errors_1.getErrorMessage)(acctErr)}`, 'error');
|
|
1313
1383
|
continue;
|
|
1314
1384
|
}
|
|
1315
|
-
validEntries.push(entry);
|
|
1316
1385
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
continue;
|
|
1386
|
+
if (validEntries.length === 0) {
|
|
1387
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false, requiresOpenOrdersSync: anyRequiresSync };
|
|
1320
1388
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const assetBPrecision = mgr.assets?.assetB?.precision;
|
|
1330
|
-
if (assetAPrecision === undefined || assetBPrecision === undefined) {
|
|
1331
|
-
mgr.logger?.log?.('Error: manager.assets precision missing in syncFromFillHistoryBatch', 'error');
|
|
1332
|
-
return { filledOrders: [], updatedOrders: [], partialFill: false, requiresOpenOrdersSync: anyRequiresSync };
|
|
1333
|
-
}
|
|
1334
|
-
for (const entry of validEntries) {
|
|
1335
|
-
const { orderId, paysAmountRaw, paysAssetId } = entry;
|
|
1336
|
-
const matchedGridOrder = this._findMatchingGridOrder(mgr, orderId);
|
|
1337
|
-
if (!matchedGridOrder) {
|
|
1338
|
-
mgr.logger.log(`[SYNC] Fill for order ${orderId} ignored: order not found in active grid.`, 'debug');
|
|
1339
|
-
anyRequiresSync = true;
|
|
1340
|
-
continue;
|
|
1389
|
+
// Phase 4: Build per-fill context + identify drift candidates
|
|
1390
|
+
const entryContexts = [];
|
|
1391
|
+
const driftOrderIds = new Set();
|
|
1392
|
+
const assetAPrecision = mgr.assets?.assetA?.precision;
|
|
1393
|
+
const assetBPrecision = mgr.assets?.assetB?.precision;
|
|
1394
|
+
if (assetAPrecision === undefined || assetBPrecision === undefined) {
|
|
1395
|
+
mgr.logger?.log?.('Error: manager.assets precision missing in syncFromFillHistoryBatch', 'error');
|
|
1396
|
+
return { filledOrders: [], updatedOrders: [], partialFill: false, requiresOpenOrdersSync: anyRequiresSync };
|
|
1341
1397
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1398
|
+
for (const entry of validEntries) {
|
|
1399
|
+
const { orderId, paysAmountRaw, paysAssetId } = entry;
|
|
1400
|
+
const matchedGridOrder = this._findMatchingGridOrder(mgr, orderId);
|
|
1401
|
+
if (!matchedGridOrder) {
|
|
1402
|
+
mgr.logger.log(`[SYNC] Fill for order ${orderId} ignored: order not found in active grid.`, 'debug');
|
|
1403
|
+
anyRequiresSync = true;
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const ctx = this._computeFillContext(mgr, matchedGridOrder, paysAssetId, paysAmountRaw);
|
|
1407
|
+
mgr.logger.log(`[SYNC] Order ${orderId} (${ctx.orderType}) currentSize=${ctx.currentSize}, filledAmount=${ctx.filledAmount}`, 'debug');
|
|
1408
|
+
if (ctx.driftSignal) {
|
|
1409
|
+
driftOrderIds.add(orderId);
|
|
1410
|
+
}
|
|
1411
|
+
entryContexts.push({
|
|
1412
|
+
entry, matchedGridOrder,
|
|
1413
|
+
orderType: ctx.orderType,
|
|
1414
|
+
precision: ctx.precision,
|
|
1415
|
+
filledAmount: ctx.filledAmount,
|
|
1416
|
+
filledAmountInt: (0, math_1.floatToBlockchainInt)(ctx.filledAmount, ctx.precision),
|
|
1417
|
+
currentSizeIntFromGrid: ctx.currentSizeIntFromGrid,
|
|
1418
|
+
rawForSaleInt: ctx.rawForSaleInt
|
|
1419
|
+
});
|
|
1346
1420
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
effectiveRawForSale: freshForSale
|
|
1370
|
-
});
|
|
1421
|
+
// Phase 5: Batch drift refetch — one get_objects call for all order IDs
|
|
1422
|
+
const refetchMap = new Map();
|
|
1423
|
+
if (driftOrderIds.size > 0) {
|
|
1424
|
+
try {
|
|
1425
|
+
const batchResults = await chainOrders.batchReadOrders([...driftOrderIds], 3000);
|
|
1426
|
+
for (const [orderId, freshOrder] of batchResults) {
|
|
1427
|
+
if (freshOrder) {
|
|
1428
|
+
const freshForSale = toFiniteNumber(freshOrder.for_sale, undefined);
|
|
1429
|
+
if (Number.isFinite(freshForSale)) {
|
|
1430
|
+
refetchMap.set(orderId, {
|
|
1431
|
+
chainConfirmsEmpty: Math.round(freshForSale) <= 0,
|
|
1432
|
+
chainRefetched: true,
|
|
1433
|
+
effectiveRawForSale: freshForSale
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
refetchMap.set(orderId, {
|
|
1438
|
+
chainConfirmsEmpty: false,
|
|
1439
|
+
chainRefetched: true,
|
|
1440
|
+
effectiveRawForSale: null
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1371
1443
|
}
|
|
1372
1444
|
else {
|
|
1373
1445
|
refetchMap.set(orderId, {
|
|
1374
|
-
chainConfirmsEmpty:
|
|
1446
|
+
chainConfirmsEmpty: true,
|
|
1375
1447
|
chainRefetched: true,
|
|
1376
1448
|
effectiveRawForSale: null
|
|
1377
1449
|
});
|
|
1378
1450
|
}
|
|
1379
1451
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1452
|
+
for (const [orderId, { chainRefetched, chainConfirmsEmpty, effectiveRawForSale }] of refetchMap) {
|
|
1453
|
+
if (chainRefetched && effectiveRawForSale != null) {
|
|
1454
|
+
mgr.logger.log(`[SYNC] Drift detected for ${orderId}; batch-refetched for_sale=${effectiveRawForSale}`, 'warn');
|
|
1455
|
+
}
|
|
1456
|
+
else if (chainConfirmsEmpty) {
|
|
1457
|
+
mgr.logger.log(`[SYNC] Drift refetch for ${orderId} returned null; chain confirms empty`, 'info');
|
|
1458
|
+
}
|
|
1386
1459
|
}
|
|
1387
1460
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
mgr.logger.log(`[SYNC] Drift detected for ${orderId}; batch-refetched for_sale=${effectiveRawForSale}`, 'warn');
|
|
1391
|
-
}
|
|
1392
|
-
else if (chainConfirmsEmpty) {
|
|
1393
|
-
mgr.logger.log(`[SYNC] Drift refetch for ${orderId} returned null; chain confirms empty`, 'info');
|
|
1394
|
-
}
|
|
1461
|
+
catch (refetchErr) {
|
|
1462
|
+
mgr.logger.log(`[SYNC] Batch drift refetch failed for ${driftOrderIds.size} orders; falling back to cache: ${refetchErr?.message || refetchErr}`, 'warn');
|
|
1395
1463
|
}
|
|
1396
1464
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
historyId,
|
|
1426
|
-
isMaker
|
|
1427
|
-
});
|
|
1428
|
-
if (result.isFull) {
|
|
1429
|
-
if (result.isGhost) {
|
|
1430
|
-
mgr.logger.log(`[SYNC] Ghost full fill for order ${orderId} (slot ${matchedGridOrder.id}): ` +
|
|
1431
|
-
`preserving orderId ${result.ghostOrderId} as PARTIAL to block duplicate CREATE.`, 'info');
|
|
1432
|
-
gridUpdates.push({ id: matchedGridOrder.id, ...result.ghostUpdate, context: 'handle-fill-ghost' });
|
|
1433
|
-
}
|
|
1434
|
-
else {
|
|
1465
|
+
// Phase 6: Compute state transitions and collect grid updates
|
|
1466
|
+
const gridUpdates = [];
|
|
1467
|
+
const filledOrders = [];
|
|
1468
|
+
const updatedOrders = [];
|
|
1469
|
+
let anyPartialFill = false;
|
|
1470
|
+
for (const ctx of entryContexts) {
|
|
1471
|
+
const { entry, matchedGridOrder, orderType, precision, filledAmount, filledAmountInt, currentSizeIntFromGrid, rawForSaleInt } = ctx;
|
|
1472
|
+
const { orderId, blockNum, historyId, isMaker } = entry;
|
|
1473
|
+
const refetchInfo = refetchMap.get(orderId);
|
|
1474
|
+
const chainRefetched = refetchInfo?.chainRefetched || false;
|
|
1475
|
+
const chainConfirmsEmpty = refetchInfo?.chainConfirmsEmpty || false;
|
|
1476
|
+
const effectiveRawForSale = refetchInfo?.effectiveRawForSale != null ? refetchInfo.effectiveRawForSale : rawForSaleInt;
|
|
1477
|
+
const result = await this._computeFillTransitionResult(mgr, {
|
|
1478
|
+
matchedGridOrder,
|
|
1479
|
+
orderType,
|
|
1480
|
+
precision,
|
|
1481
|
+
filledAmount,
|
|
1482
|
+
filledAmountInt,
|
|
1483
|
+
currentSizeIntFromGrid,
|
|
1484
|
+
rawForSaleInt,
|
|
1485
|
+
chainRefetched,
|
|
1486
|
+
chainConfirmsEmpty,
|
|
1487
|
+
effectiveRawForSale,
|
|
1488
|
+
blockNum,
|
|
1489
|
+
historyId,
|
|
1490
|
+
isMaker
|
|
1491
|
+
});
|
|
1492
|
+
if (result.isFull) {
|
|
1435
1493
|
mgr.logger.log(`[SYNC] Full fill for order ${orderId} (slot ${matchedGridOrder.id}).`, 'info');
|
|
1436
1494
|
gridUpdates.push({ id: matchedGridOrder.id, ...result.fullUpdate, context: 'handle-fill-full' });
|
|
1495
|
+
filledOrders.push(result.filledOrder);
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
mgr.logger.log(`[SYNC] Partial fill for order ${orderId} (slot ${matchedGridOrder.id}): newSize=${result.newSize}`, 'info');
|
|
1499
|
+
gridUpdates.push({ id: matchedGridOrder.id, ...result.partialUpdate, context: 'handle-fill-partial' });
|
|
1500
|
+
updatedOrders.push(result.partialUpdate);
|
|
1501
|
+
filledOrders.push(result.filledOrder);
|
|
1502
|
+
anyPartialFill = true;
|
|
1437
1503
|
}
|
|
1438
|
-
filledOrders.push(result.filledOrder);
|
|
1439
1504
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1505
|
+
// Phase 7: Single grid batch update — acquires _gridLock once
|
|
1506
|
+
if (gridUpdates.length > 0) {
|
|
1507
|
+
const updateObjects = gridUpdates.map(u => {
|
|
1508
|
+
const { context, ...orderData } = u;
|
|
1509
|
+
return orderData;
|
|
1510
|
+
});
|
|
1511
|
+
const batchOk = await mgr.applyGridUpdateBatch(updateObjects, 'handle-fill-batch', { skipAccounting: false, fee: 0 });
|
|
1512
|
+
if (batchOk === false) {
|
|
1513
|
+
mgr.logger.log('[SYNC] Batch grid update failed for some fills; marking totals stale for next sync cycle', 'warn');
|
|
1514
|
+
mgr.accountTotalsStale = true;
|
|
1515
|
+
}
|
|
1446
1516
|
}
|
|
1517
|
+
// Re-anchor accountTotals to authoritative post-fill chain truth
|
|
1518
|
+
// after the batch's accounting + grid mutation. The up-front
|
|
1519
|
+
// stale-totals refresh returns post-fill balances, so the batch's
|
|
1520
|
+
// processFillAccounting pays/receives adjustments would otherwise
|
|
1521
|
+
// double-count; the forced fetch overwrites them with the
|
|
1522
|
+
// authoritative values and hands re-commitments
|
|
1523
|
+
// (tryDeductFromChainFree) a fresh snapshot.
|
|
1524
|
+
await mgr.reanchorAccountTotals('fill-batch');
|
|
1525
|
+
return { filledOrders, updatedOrders, partialFill: anyPartialFill, requiresOpenOrdersSync: anyRequiresSync };
|
|
1447
1526
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
});
|
|
1454
|
-
const batchOk = await mgr.applyGridUpdateBatch(updateObjects, 'handle-fill-batch', { skipAccounting: false, fee: 0 });
|
|
1455
|
-
if (batchOk === false) {
|
|
1456
|
-
mgr.logger.log('[SYNC] Batch grid update failed for some fills; marking totals stale for next sync cycle', 'warn');
|
|
1457
|
-
mgr.accountTotalsStale = true;
|
|
1458
|
-
}
|
|
1527
|
+
finally {
|
|
1528
|
+
// Lower the guard before the final consolidated recalc so the
|
|
1529
|
+
// settled (re-anchored) state is still verified.
|
|
1530
|
+
mgr._fillBatchInFlight = Math.max(0, (mgr._fillBatchInFlight ?? 0) - 1);
|
|
1531
|
+
await mgr.resumeFundRecalc();
|
|
1459
1532
|
}
|
|
1460
|
-
return { filledOrders, updatedOrders, partialFill: anyPartialFill, requiresOpenOrdersSync: anyRequiresSync };
|
|
1461
1533
|
}
|
|
1462
1534
|
finally {
|
|
1463
|
-
|
|
1535
|
+
mgr.unlockOrders(allOrderIds);
|
|
1464
1536
|
}
|
|
1465
1537
|
}
|
|
1466
1538
|
finally {
|
|
1467
|
-
|
|
1539
|
+
// Safety net: ensure the guard never leaks across cycles on early
|
|
1540
|
+
// returns (deferral) or exceptions in the refresh/lock region.
|
|
1541
|
+
if ((mgr._fillBatchInFlight ?? 0) > 0)
|
|
1542
|
+
mgr._fillBatchInFlight--;
|
|
1468
1543
|
}
|
|
1469
1544
|
}
|
|
1470
1545
|
/**
|
|
@@ -1555,14 +1630,21 @@ class SyncEngine {
|
|
|
1555
1630
|
}
|
|
1556
1631
|
}
|
|
1557
1632
|
const newState = isPartialPlacement ? constants_1.ORDER_STATES.PARTIAL : constants_1.ORDER_STATES.ACTIVE;
|
|
1558
|
-
|
|
1633
|
+
let normalizedExpectedType = (expectedType === constants_1.ORDER_TYPES.BUY || expectedType === constants_1.ORDER_TYPES.SELL)
|
|
1559
1634
|
? expectedType
|
|
1560
1635
|
: null;
|
|
1636
|
+
// Defensive: a SPREAD slot must never transition to an on-chain
|
|
1637
|
+
// state (validateOrder rejects SPREAD+ACTIVE/PARTIAL as fatal).
|
|
1638
|
+
// Derive the side from the slot price vs start price — same
|
|
1639
|
+
// convention as the funds check (manager.ts).
|
|
1640
|
+
if (!normalizedExpectedType && gridOrder.type === constants_1.ORDER_TYPES.SPREAD) {
|
|
1641
|
+
normalizedExpectedType = (0, order_1.resolveSpreadOrderSide)(gridOrder.price, mgr.config.startPrice);
|
|
1642
|
+
}
|
|
1561
1643
|
const updatedOrder = {
|
|
1562
1644
|
...gridOrder,
|
|
1563
1645
|
type: normalizedExpectedType || gridOrder.type,
|
|
1564
1646
|
state: newState,
|
|
1565
|
-
orderId: chainOrderId
|
|
1647
|
+
orderId: chainOrderId,
|
|
1566
1648
|
};
|
|
1567
1649
|
// Restore btsFeeState from raw chain order data if provided.
|
|
1568
1650
|
// After a grid reset, in-memory orders have no fee state but the
|