fix: cancel all open orders before sell to release CLOB locked balance

Root cause: auto-sell GTC orders lock all shares in the CLOB's internal
ledger. When executeSell runs, it tried to cancel only position.sellOrderId
but this can fail silently, leaving tokens locked. The CLOB then rejects
the new sell with "not enough balance or allowance" because the balance
is committed to the existing GTC order.

Fix:
- Fetch all open orders for the specific tokenId via client.getOpenOrders()
- Cancel all of them with Promise.allSettled (non-fatal per order)
- Wait 600ms after cancellation so the CLOB updates its locked-balance
  ledger before we place the new sell
- Fallback: if getOpenOrders fails, still attempt to cancel by
  position.sellOrderId (previous behaviour) then wait 600ms
- Use correct cancelOrder({ orderID }) object form throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
direkturcrypto
2026-02-25 03:38:38 +07:00
co-authored by Claude Sonnet 4.6
parent ec2c948a00
commit 097148c501
+22 -8
View File
@@ -306,15 +306,29 @@ export async function executeSell(trade) {
return;
}
// Cancel existing auto-sell order if any
if (position.sellOrderId) {
try {
const client = getClient();
await client.cancelOrder(position.sellOrderId);
logger.info(`Cancelled auto-sell order: ${position.sellOrderId}`);
} catch (err) {
logger.warn(`Failed to cancel auto-sell: ${err.message}`);
// Cancel ALL open orders for this token so the CLOB frees up locked balance.
// Only cancelling by sellOrderId is not enough — the cancel can fail silently
// and locked tokens cause "not enough balance" on the subsequent sell.
const client = getClient();
try {
const openOrders = await client.getOpenOrders({ asset_id: tokenId });
if (Array.isArray(openOrders) && openOrders.length > 0) {
logger.info(`Cancelling ${openOrders.length} open order(s) for token before sell`);
await Promise.allSettled(
openOrders.map((o) => client.cancelOrder({ orderID: o.id ?? o.order_id })),
);
// Brief pause so the CLOB can update the locked-balance ledger
await new Promise((r) => setTimeout(r, 600));
}
} catch (err) {
// Fallback: try to cancel just the tracked auto-sell order ID
if (position.sellOrderId) {
try {
await client.cancelOrder({ orderID: position.sellOrderId });
await new Promise((r) => setTimeout(r, 600));
} catch { /* ignore */ }
}
logger.warn(`Could not fetch open orders to cancel: ${err.message}`);
}
updatePosition(effectiveConditionId, { status: 'selling' });