fix(perps): wait for the client's init() across a disconnect-then-init reconnect - #10589
Merged
Merged
Conversation
Orders submitted while the Perps connection reconnects failed with CLIENT_NOT_INITIALIZED although the HTTP exchange client stays available. - Read order-path data over HTTP: margin-mode lock, HIP-3 balances and spot metadata, unified-account and referral setup, asset-map rebuilds, and the #ensureReady gate. getMaxLeverage no longer requires the WebSocket clients, which forced a 3x cap during a reconnect. - When an action waited on a disconnect or reinitialization, wait up to ConnectionTimeoutMs for the client's follow-up init() instead of failing in the gap between disconnect() and init(). Refs: TAT-4041
Retry rate-limited pre-order HTTP reads with jittered backoff.
abretonc7s
marked this pull request as ready for review
September 30, 2026 00:10
abretonc7s
enabled auto-merge
September 30, 2026 00:10
Keep only the controller change: an action waiting on disconnect() waits a bounded time for the client's follow-up init(), and fails with PROVIDER_LIFECYCLE_STALE if the account, network or provider changed. The HTTP order-path reads, the 429 retry and the getMaxLeverage change move to a stacked follow-up so the transport choice can be validated separately. Refs: TAT-4041
3 of 4 tasks
Bigshmow
approved these changes
Sep 30, 2026
Bigshmow
left a comment
Contributor
There was a problem hiding this comment.
I see the connection timeout constant is used in this method invocation but left a comment about using it as a default instead. Otherwise, LGTM.
| * @param timeoutMs - Longest time to wait for init() to be called. | ||
| * @returns A promise that resolves when init starts or the timeout elapses. | ||
| */ | ||
| async #waitForInitializationStart(timeoutMs: number): Promise<void> { |
Contributor
There was a problem hiding this comment.
nit: wdyt about a default for timeoutMs here to inform callers in the future?
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Sep 30, 2026
## Explanation - Release major perps-controller 19.0.0 Breaking changes (see changelog): - `OrderFill.liquidation.liquidatedUser` is now optional - `FrontendOrder.orderType` union adds `Twap Slice`, `Vault Close`, `Spot Dust Conversion` - Removed `LighterPersonalSigner` and the `personalSigner` / `l1Address` fields of `LighterAuthConfig` ## References - #10414, #10437, #10464, #10559, #10588, #10589, #10591 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Explanation
Mobile reconnects Perps with
PerpsController.disconnect(), a short cleanup delay, theninit(). That covers a foreground resume after a failed ping, and account or network changes.#getActiveProviderWhenReadywaited for the disconnect but not for theinit()that follows it. An order submitted in that window woke up to an uninitialized controller and failed withCLIENT_NOT_INITIALIZED.This PR changes only the controller:
disconnect()and the controller is still uninitialized afterwards, it waits up toPERPS_CONSTANTS.ConnectionTimeoutMsfor the client'sinit()to start, then runs. If noinit()follows, it fails as before. The controller never starts a connection itself.PROVIDER_LIFECYCLE_STALEinstead of running under the new context. The context is recorded when the action is issued.Behavior change: after a final disconnect with no follow-up
init(), an action that was already in flight now takes up to 10 s to fail instead of failing right away. Actions issued after the disconnect are unaffected.The provider-side part of TAT-4041 (order-path reads over HTTP during a WebSocket reconnect, the
getMaxLeverage3x cap, 429 retry) is split into the stacked draft #10590. It stays in draft until a transport stress test settles the long-term transport choice.Not in this PR:
PROVIDER_LIFECYCLE_STALEfor an order already in flight inside the provider when the account changes. Continuing under the new account would be wrong. How that code is shown to the user is up to the client.Reproduction
The real headless
PerpsController, SDK transports and signer ran against HyperLiquid testnet, with a client driver calling the realdisconnect()and a delayedinit().1f15b7c)disconnect()followed by delayedinit()CLIENT_NOT_INITIALIZEDat 2,970 ms.init()only started at 3,470 ms.init()finished at 5,260 ms. Order61421723850succeeded at 7,882 ms, was observed open, and was canceled by ID. 0 BTC orders and 0 positions after teardown.That run was recorded on
9eb852d, which also contained the provider changes now in #10590. The controller code on this path is the same apart from narrowing: the wait now only follows adisconnect(), not a reinitialization. I have not rerun the testnet reproduction on9149042. No Mobile or Extension UI was exercised.Validation (
9149042)PerpsControllersuites:8 passed,465 passed.main: order placed across disconnect then init, bounded wait, and refusal after an account switch or a network switch. With only the context guard removed, just the 2 switch tests fail.oxlintandoxfmtclean;changelog:validatepasses; the package build type-checks.References
PROVIDER_LIFECYCLE_STALEon account switch), TAT-3871 (retry and idempotency of the exchange call)Checklist