Skip to content

improve(hyperliquid): enrich excessive-slippage warn with pair + blocked SwapFlows#3439

Open
droplet-rl wants to merge 2 commits into
masterfrom
droplet/T90K0AL22-C0AQEHGS56Z-1780148503-175839-slippage-logs
Open

improve(hyperliquid): enrich excessive-slippage warn with pair + blocked SwapFlows#3439
droplet-rl wants to merge 2 commits into
masterfrom
droplet/T90K0AL22-C0AQEHGS56Z-1780148503-175839-slippage-logs

Conversation

@droplet-rl
Copy link
Copy Markdown
Contributor

Summary

Enriches the Not submitting new limit order due to excessive slippage warning emitted by HyperliquidExecutor#updateOrderAmount so a single log line is enough to identify which pair, which SwapHandler, and which user-level SwapFlows are being held up by the rejection. Today the warning only carries bestAsk and maxSlippage — for example, see this prod fire: https://risklabs.slack.com/archives/C0A15N2Q755/p1780111530258689

Before

[warn] zion-across-hyperliquid-executor (HyperliquidExecutor#updateOrderAmount)
  Not submitting new limit order due to excessive slippage
  - bestAsk:      0.9990
  - maxSlippage:  5.00

To answer "whose deposits are stuck?" we had to grep config for the pair, look up the SwapHandler on-chain, and re-query SwapFlowInitialized events ourselves.

After

The warn now includes:

  • pair, baseToken, finalToken, swapHandler — affected pair + holding contract, no config grep needed.
  • orderSize — would-be order size in baseToken human units.
  • bpsFromParity — actual slippage observed, so it's clear how far over the threshold we are.
  • maxSlippage — now correctly reflects the route-specific override (maxSlippageByRoute[pair.name] ?? maxSlippageBps); previously always logged the global default even when an override was active.
  • blockedSwapFlows — array of { quoteNonce, finalRecipient, evmAmountIn } for each outstanding (un-finalized) SwapFlowInitialized on the pair. This is the "which user is being held up" signal the warning is silent about today.

Cost

getOutstandingOrdersOnPair(pair) does two paginatedEventQuery calls against HyperEVM RPC. It runs only inside the warn branch (slippage actually exceeded), not on every block in steady state. Net cost in normal operation: zero.

Test plan

  • yarn typecheck — clean locally.
  • yarn prettier --check src/hyperliquid/HyperliquidExecutor.ts — clean.
  • Operator deploy — verify the warn line in Slack now carries pair, swapHandler, blockedSwapFlows, etc. Should fire on the same conditions as before (no behavioral change).

Related

Came out of the broader investigation in https://app.slack.com/client/T90K0AL22/C0AQEHGS56Z/thread/C0AQEHGS56Z-1780148503.175839 (stuck OFT/HyperCore deposit). Independent of #3438 (lookback default bump) — open as a separate PR so each can land on its own merits.

🤖 Generated with Claude Code

…ked SwapFlows

The "Not submitting new limit order due to excessive slippage" warning
fired from HyperliquidExecutor#updateOrderAmount currently only carries
bestAsk + maxSlippage, with no indication of which pair is affected,
which SwapHandler is queueing, or which user-level SwapFlows are stuck
behind the rejection. That makes any "my deposit isn't moving" question
into a manual cross-reference against open orders and chain state.

Extend the log with:

- pair / baseToken / finalToken / swapHandler — so the affected pair
  and underlying contract are obvious without grepping config.
- orderSize — the would-be order size, in baseToken human units.
- bpsFromParity — the actual slippage we saw, so it's clear how far over
  the limit we are (not just that we were over).
- blockedSwapFlows — quoteNonce, finalRecipient, evmAmountIn for each
  outstanding (un-finalized) SwapFlowInitialized on the pair. This is
  the "which user is affected" data the warning is silent about today.

Also fix the existing maxSlippage field, which read the global
maxSlippageBps even when a route-specific override was in effect — log
now reflects the threshold actually applied.

The extra getOutstandingOrdersOnPair fetch is in the warn branch only,
so the hot path is unchanged when slippage is within tolerance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac9d8e43db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hyperliquid/HyperliquidExecutor.ts Outdated
const sizeFormatter = createFormatFunction(2, 4, false, pair.baseTokenDecimals);
// Fetch the user-level SwapFlows blocked by this rejection so the log identifies
// which deposits are held up. Only done in this warning branch to keep the hot path cheap.
const blockedSwapFlows = await this.getOutstandingOrdersOnPair(pair);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Query current blocks for blocked-flow logging

When this warning fires in the long-running executor after startup, calling getOutstandingOrdersOnPair(pair) without a to value uses the dstSearchConfig.to block captured during initialize(), so any SwapFlowInitialized events observed after that startup block are excluded from blockedSwapFlows. This means the new log often omits the exact deposits that triggered the slippage rejection; pass a live/current block (or allow the query to use latest) for this logging path.

Useful? React with 👍 / 👎.

Comment thread src/hyperliquid/HyperliquidExecutor.ts Outdated
const sizeFormatter = createFormatFunction(2, 4, false, pair.baseTokenDecimals);
// Fetch the user-level SwapFlows blocked by this rejection so the log identifies
// which deposits are held up. Only done in this warning branch to keep the hot path cheap.
const blockedSwapFlows = await this.getOutstandingOrdersOnPair(pair);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep slippage rejection independent of log enrichment

When the market is outside the configured slippage bound, this new event-query is only for enriching the warning, but any dst RPC/pagination failure here now rejects updateOrderAmount before it can return the non-actionable result. Since processTasks awaits task promises without a catch, a transient failure in this observability-only query can stop the executor in exactly the backoff path where it previously just logged and continued; wrap this enrichment so failures fall back to an empty/partial blockedSwapFlows log.

Useful? React with 👍 / 👎.

… enrichment

Address Codex review feedback on #3439:
- Query getOutstandingOrdersOnPair with the current dst block instead of
  the dstSearchConfig.to captured at initialize(), so SwapFlows initialized
  after startup are not omitted from the warning.
- Wrap the enrichment in try/catch and fall back to an empty list so a
  transient dst RPC/pagination failure cannot reject updateOrderAmount and
  stop processTasks.
@droplet-rl
Copy link
Copy Markdown
Contributor Author

@codex addressed both comments in 2e8e18d:

  1. Stale to block: switched the enrichment call to getOutstandingOrdersOnPair(pair, latestBlock), fetching the live dstProvider.getBlockNumber() so SwapFlowInitialized events arriving after initialize() are included in blockedSwapFlows.
  2. Enrichment failure aborting the task: wrapped the fetch in try/catch with a fallback to [] and a debug log. A transient dst RPC / pagination error now degrades to a warning without blockedSwapFlows instead of throwing out of updateOrderAmount and killing processTasks.

yarn typecheck clean; lint + prettier clean.

@chatgpt-codex-connector
Copy link
Copy Markdown

Follow-up Result

I reviewed the current PR head (2e8e18d) and confirmed the two requested review comments are already addressed in src/hyperliquid/HyperliquidExecutor.ts:

No code changes were needed, so I did not create a commit or open a new PR.

Testing / Checks

  • ⚠️ nl -ba src/hyperliquid/HyperliquidExecutor.ts | sed -n '438,486p;560,585p' && yarn typecheck — source inspection succeeded, but yarn typecheck could not run in this checkout because Yarn reported the workspace package is missing from the lockfile and suggested running yarn install.
  • git status --short — confirmed the working tree is clean.

View task →

@droplet-rl
Copy link
Copy Markdown
Contributor Author

Thanks for the confirmation — no further action needed on my end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant