feat(perps): expose margin-mode lock and supported margin modes per market - #10414
Merged
Merged
Conversation
Add PerpsController.getMarginModeLock and the optional PerpsProvider.getMarginModeLock, reporting the margin mode an asset is bound to by an open position or a resting order/TWAP. HyperLiquid reads it through the same helper order validation uses, so clients can keep their margin-mode picker in sync with what placement accepts.
Add optional supportedMarginModes to ready order capabilities so clients stop inferring margin-mode support from the provider. HyperLiquid derives it from the same Cross rule order validation uses. Implement getMarginModeLock for Lighter from the open position's mode.
3 of 4 tasks
HyperLiquid now resolves the selected account once for the lock read and reports it unavailable if the account changes mid-read, so positions and orders from different accounts are never combined. Lighter treats an account response without the account or its positions as unavailable rather than unlocked. Clarify that unlocked does not imply Cross support.
abretonc7s
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Sep 24, 2026
Drop the stored lock answer when Cross availability turns off so it cannot be reused on re-enable, re-read the lock when the Pro screen regains focus, and bring the temporary controller patch in line with MetaMask/core#10414 (one account per lock read, dedicated unavailable helper).
The margin-mode lock read now confirms the selected account is unchanged after activeAssetData resolves, so a switch during that call reports the lock as unavailable instead of returning the previous account's mode.
abretonc7s
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Sep 24, 2026
Match MetaMask/core#10414: re-check the selected account after the asset-mode read so a switch during it reports the lock as unavailable.
abretonc7s
enabled auto-merge
September 24, 2026 08:11
8 of 10 tasks
8 of 10 tasks
abretonc7s
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Sep 25, 2026
Read the market's margin-mode lock from PerpsController.getMarginModeLock (backported to 17.1.0 through a temporary patch until MetaMask/core#10414 ships) so the picker follows a resting order or TWAP, not only an open position. Answers are keyed by market, account and network; while the lock is unknown or unavailable the picker and Place Order stay held.
abretonc7s
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Sep 25, 2026
Drop the stored lock answer when Cross availability turns off so it cannot be reused on re-enable, re-read the lock when the Pro screen regains focus, and bring the temporary controller patch in line with MetaMask/core#10414 (one account per lock read, dedicated unavailable helper).
abretonc7s
added a commit
to MetaMask/metamask-mobile
that referenced
this pull request
Sep 25, 2026
Match MetaMask/core#10414: re-check the selected account after the asset-mode read so a switch during it reports the lock as unavailable.
This was referenced Sep 25, 2026
Closed
geositta
previously approved these changes
Sep 28, 2026
cryptodev-2s
pushed a commit
to cryptodev-2s/core
that referenced
this pull request
Sep 28, 2026
> This persists the chosen preference; MetaMask#10414 reads the venue-enforced lock. The canonical Mobile Cross-margin PR is MetaMask/metamask-mobile#36881. Wiring Mobile to the persistence API remains follow-up work and must preserve venue-lock precedence. ## Explanation In the Pro order form, leverage and order-book grouping are saved per market in `tradeConfigurations[network][symbol]`. The Isolated/Cross pick is the exception: Mobile keeps it in local React state, so it resets to Isolated whenever the Pro screen remounts, and Extension never sees it. This adds the pick to the same per-market, per-network entry: - `tradeConfigurations[network][symbol].marginMode` (`'isolated' | 'cross'`), persisted with the rest of `tradeConfigurations`. No migration needed; the field is optional and absent means "no saved pick". - `getMarginMode(symbol)` and `saveMarginMode(symbol, marginMode)`, exposed as `PerpsController:getMarginMode` / `PerpsController:saveMarginMode`. Saving merges into the existing entry, so leverage, grouping and `pendingConfig` are kept. Values other than `isolated`/`cross` are ignored, so a bad call can't persist something that would later reach `placeOrder({ marginMode })`. - `selectMarginMode(state, symbol)` selector, plus `marginMode?` on `TradeConfiguration`. Keying: network + market, same as leverage, not account. The mode is tied to leverage in the form, so keeping both on one key stops them drifting apart. The account-specific constraint (an open position or resting order fixes the mode) is the venue lock, which clients already apply on top of the saved pick. `placeOrder` behaviour is unchanged. Validation: new controller and selector unit tests; a headless run that saves picks on the real controller, rebuilds it from the persisted state slice, and checks the picks come back, leverage is kept, and mainnet/testnet stay separate. Root `yarn build` passes and the new symbols are in `dist`. ## References - https://consensyssoftware.atlassian.net/browse/TAT-4015 - Follow-up to TAT-3524 / MetaMask/metamask-mobile#36881, the consolidated Mobile Cross-margin PR. - Mobile follow-up: `usePerpsProOrderForm` should read `selectMarginMode` as the default and write with `saveMarginMode` instead of local `marginModeSelection`, keeping `getMarginModeLock` precedence. ## **Validation Recipe** <details><summary>recipe.json (0 steps — TAT-4015 persisted Pro margin mode)</summary> ```json { "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json", "title": "TAT-4015 persisted Pro margin mode", "description": "Proves PerpsController saves and restores the Isolated/Cross pick per market and network through its messenger actions, across a rebuild from persisted state, without disturbing leverage.", "workflow": { "entry": "run-driver", "nodes": { "run-driver": { "action": "command", "timeout_ms": 180000, "intent": "Drive the real PerpsController from this checkout: save/read margin mode, rebuild from the persisted slice, then switch network", "next": "driver-ok" }, "driver-ok": { "action": "assert_exit_code", "source": "run-driver", "expected": 0, "intent": "The driver completed; it throws if saveMarginMode/getMarginMode are missing", "next": "c1-btc-cross" }, "c1-btc-cross": { "action": "assert_json", "assert": { "path": "$.sameSession.btc", "operator": "eq", "value": "cross" }, "intent": "C1: BTC pick saved as cross is read back on mainnet", "next": "c1-eth-isolated" }, "c1-eth-isolated": { "action": "assert_json", "assert": { "path": "$.sameSession.eth", "operator": "eq", "value": "isolated" }, "intent": "C1+C5: ETH keeps isolated after an invalid 'portfolio' save is ignored", "next": "c3-unset-market" }, "c3-unset-market": { "action": "assert_json", "assert": { "path": "$.sameSession.sol", "operator": "eq", "value": "unset" }, "intent": "C3: a market with no saved pick returns undefined (client falls back to Isolated)", "next": "c4-leverage-kept" }, "c4-leverage-kept": { "action": "assert_json", "assert": { "path": "$.sameSession.btcLeverage", "operator": "eq", "value": 7 }, "intent": "C4: saving margin mode does not drop the market's saved leverage", "next": "c2-restart-btc" }, "c2-restart-btc": { "action": "assert_json", "assert": { "path": "$.afterRestart.btc", "operator": "eq", "value": "cross" }, "intent": "C2: BTC cross pick survives rebuilding the controller from persisted state", "next": "c2-restart-eth" }, "c2-restart-eth": { "action": "assert_json", "assert": { "path": "$.afterRestart.eth", "operator": "eq", "value": "isolated" }, "intent": "C2: ETH isolated pick survives rebuilding from persisted state", "next": "c2-restart-leverage" }, "c2-restart-leverage": { "action": "assert_json", "assert": { "path": "$.afterRestart.btcLeverage", "operator": "eq", "value": 7 }, "intent": "C2+C4: leverage persists alongside margin mode", "next": "c3-testnet-empty" }, "c3-testnet-empty": { "action": "assert_json", "assert": { "path": "$.networkScope.testnetBtcBeforeSave", "operator": "eq", "value": "unset" }, "intent": "C3: mainnet BTC pick does not leak to testnet", "next": "c3-testnet-saved" }, "c3-testnet-saved": { "action": "assert_json", "assert": { "path": "$.networkScope.testnetBtcAfterSave", "operator": "eq", "value": "isolated" }, "intent": "C3: testnet BTC pick is stored independently", "next": "c3-mainnet-untouched" }, "c3-mainnet-untouched": { "action": "assert_json", "assert": { "path": "$.networkScope.mainnetBtcInState", "operator": "eq", "value": "cross" }, "intent": "C3: testnet save leaves the mainnet BTC entry unchanged", "next": "unit-tests" }, "unit-tests": { "action": "command", "cmd": "yarn workspace @metamask/perps-controller run jest packages/perps-controller/tests/src/PerpsController.configuration.test.ts packages/perps-controller/tests/src/selectors.test.ts -t 'argin mode' --no-coverage --reporters=default --verbose", "timeout_ms": 600000, "intent": "C6: run the targeted margin-mode unit tests (controller methods, messenger exposure via init, selector)", "next": "unit-tests-ok" }, "unit-tests-ok": { "action": "assert_exit_code", "source": "unit-tests", "expected": 0, "intent": "C6: targeted unit tests pass", "next": "unit-tests-ran" }, "unit-tests-ran": { "action": "assert_output", "node": "unit-tests", "match": "Tests: +\\d+ skipped, 10 passed", "intent": "C6: exactly the 10 margin-mode tests ran and passed (not a zero-test pass)", "next": "done", "stream": "stderr" }, "done": { "action": "end", "status": "pass" } } } } ``` </details> ## **Validation Logs** <details><summary>Full output (16/16 passed, pass)</summary> ``` # MetaMask Recipe Run Status: pass Duration: 3.3s Nodes: 16/16 passed ## Steps - PASS run-driver (command, 1.1s): exitCode=0, stdout={"sameSession":{"btc":"cross","eth":"isolated","sol":"unset","btcLeverage":7},"afterRestart":{"btc":"cross","eth":"isolated","btcLeverage":7},"networkScope":{"testnetBtcBeforeSave":"unset","testnetBtcAfterSave":"isolated","mainnetBtcInState":"cross"}} - PASS driver-ok (assert_exit_code, 0ms): source=run-driver, expected=0, actual=0 - PASS unit-tests (command, 2.2s): exitCode=0, stderr=PASS perps-controller tests/src/PerpsController.configuration.test.ts PerpsController toggleTestnet ○ skipped returns error when already reinitializing ○ skipped toggles to testnet network ○ skipped returns failure and rolls back isTestnet when init sets InitializationState.Failed ○ skipped clears isReinitializing flag after init failure market filter preferences ○ skipped saves and retrieves filter preference ○ skipped saves and retrieves price change with ascending direction pro layout preferences ○ skipped defaults to collapsed order book, expanded chart, reserved positions, and positions/orders sort/filter defaults ○ skipped updates a single field without clobbering the others ○ skipped merges successive partial patches ○ skipped updates sort field without clobbering sort direction ○ skipped updates orders sort field without clobbering orders sort direction or positions sort ○ skipped updates orders side filter without clobbering positions side filter ○ skipped persists the update to controller state ○ skipped fills in defaults for fields missing from persisted state order book preferences ○ skipped defaults to USD totals ○ skipped updates a single preference without clobbering the other ○ skipped fills in defaults for fields missing from persisted state selected order type ○ skipped defaults to market and persists independently of market visible candle count ○ skipped defaults to 30 and persists a valid count ○ skipped clamps to the supported range and rounds to a whole candle ○ skipped ignores non-finite values perps mode ○ skipped defaults to lite mode ○ skipped sets the mode to pro ○ skipped sets the mode back to lite watchlist management ○ skipped adds and removes market from watchlist resetFirstTimeUserState ○ skipped resets tutorial and order state for both networks clearPendingTransactionRequests ○ skipped removes pending and bridging withdrawal requests ○ skipped removes pending and bridging deposit requests ○ skipped resets withdrawal progress ○ skipped handles empty arrays gracefully trade configuration ○ skipped returns undefined for unsaved configuration ○ skipped retrieves saved configuration pending trade configuration ○ skipped saves pending trade configuration ○ skipped restores a short direction from a pending trade configuration ○ skipped returns undefined for non-existent pending configuration ○ skipped returns undefined for expired pending configuration (more than 30 seconds) ○ skipped returns configuration for valid pending configuration (less than 30 seconds) ○ skipped clears expired pending configuration automatically ○ skipped clears pending trade configuration explicitly ○ skipped clears only the draft while retaining leverage and selected order type ○ skipped clears the draft after a successful order ○ skipped retains the draft after an unsuccessful order ○ skipped saves pending config per network (testnet vs mainnet) ○ skipped preserves existing leverage when saving pending config WebSocket connection state ○ skipped getWebSocketConnectionState returns state from active provider ○ skipped getWebSocketConnectionState returns DISCONNECTED when provider does not support method ○ skipped getWebSocketConnectionState returns DISCONNECTED when no provider is active ○ skipped subscribeToConnectionState delegates to active provider ○ skipped subscribeToConnectionState calls listener immediately when provider does not support method ○ skipped subscribeToConnectionState returns no-op when no provider is active ○ skipped reconnect delegates to active provider ○ skipped reconnect does nothing when provider does not support method ○ skipped reconnect does nothing when no provider is active order book grouping ○ skipped saves order book grouping for mainnet ○ skipped saves order book grouping for testnet ○ skipped returns undefined when no grouping is saved ○ skipped preserves existing config when saving grouping margin mode ✓ saves margin mode for mainnet (8 ms) ✓ saves margin mode for testnet without touching mainnet (3 ms) ✓ returns undefined when no margin mode is saved (2 ms) ✓ overwrites the previous margin mode for the same market (1 ms) ✓ preserves existing config when saving margin mode (3 ms) ✓ ignores values that are not a margin mode (1 ms) ✓ exposes margin mode methods as messenger actions (4 ms) standalone mode getPositions with standalone mode ○ skipped uses existing provider for standalone queries when available ○ skipped creates temporary provider for standalone queries when no activeProviderInstance ○ skipped bypasses getActiveProvider check for standalone queries getAccountState with standalone mode ○ skipped uses existing provider for standalone queries when available ○ skipped creates temporary provider for standalone queries when no activeProviderInstance ○ skipped bypasses getActiveProvider check for standalone queries standalone provider caching ○ skipped reuses the same standalone provider across multiple calls ○ skipped cleans up standalone provider on init() ○ skipped invalidates cached provider when isTestnet changes ○ skipped cleans up standalone provider on disconnect() ○ skipped cleans up standalone provider on stopMarketDataPreload() setSelectedPaymentToken ○ skipped sets selectedPaymentToken to null when passed null ○ skipped sets selectedPaymentToken to null when token has PerpsBalanceTokenDescription ○ skipped stores description, address and chainId when passed a normal token PASS perps-controller tests/src/selectors.test.ts PerpsController selectors selectIsFirstTimeUser ○ skipped returns true when state is undefined ○ skipped returns true when isFirstTimeUser is true ○ skipped returns false when isFirstTimeUser is false ○ skipped returns true when isFirstTimeUser is undefined in state selectTradeConfiguration ○ skipped returns saved config for mainnet when not testnet ○ skipped returns saved config for testnet when testnet ○ skipped returns undefined when no config exists for asset ○ skipped returns undefined when config exists but has no leverage ○ skipped returns config for specific asset when multiple assets configured selectWatchlistMarkets ○ skipped returns mainnet watchlist when not on testnet ○ skipped returns testnet watchlist when on testnet ○ skipped returns empty array when watchlist is undefined selectIsWatchlistMarket ○ skipped returns true when market is in watchlist ○ skipped returns false when market is not in watchlist selectHasPlacedFirstOrder ○ skipped returns mainnet value when not on testnet ○ skipped returns testnet value when on testnet ○ skipped returns false when hasPlacedFirstOrder is undefined selectMarketFilterPreferences ○ skipped returns saved filter preferences when defined ○ skipped returns default preferences when preference is undefined ○ skipped handles legacy string format (backward compatibility) ○ skipped handles legacy compound ID priceChange-desc (backward compatibility) ○ skipped handles legacy compound ID priceChange-asc (backward compatibility) ○ skipped handles other legacy simple strings (backward compatibility) selectPendingTradeConfiguration ○ skipped returns pending config for mainnet when not testnet and not expired ○ skipped returns undefined for expired pending config (more than 30 seconds) ○ skipped returns pending config for valid config (less than 30 seconds) ○ skipped returns undefined when no pending config exists ○ skipped returns pending config for testnet when on testnet ○ skipped returns undefined when config exists but has no pendingConfig ○ skipped applies the TTL on every call even when the memoized inputs are unchanged ○ skipped returns a stable reference across re-selection while the draft is valid selectOrderBookGrouping ○ skipped returns mainnet order book grouping when not on testnet ○ skipped returns testnet order book grouping when on testnet ○ skipped returns undefined when no config exists for asset ○ skipped returns undefined when orderBookGrouping is not set selectMarginMode ✓ returns the mainnet margin mode when not on testnet (2 ms) ✓ returns the testnet margin mode when on testnet ✓ returns undefined when margin mode is not set selectRecentlyViewedMarkets ○ skipped returns mainnet symbols when not on testnet, ordered newest-first ○ skipped returns testnet symbols when on testnet ○ skipped filters out entries older than 24 hours ○ skipped returns empty array when all entries are expired ○ skipped returns empty array when recentlyViewedMarkets is undefined ○ skipped caps results at 10 items selectProLayoutPreferences ○ skipped returns the pro-mode layout preferences ○ skipped merges persisted fields over defaults so missing fields fall back ○ skipped returns defaults when the state slice is missing ○ skipped returns defaults when state is undefined selectOrderBookPreferences ○ skipped merges persisted fields over defaults ○ skipped returns defaults when the state slice is missing selectSelectedOrderType ○ skipped returns the persisted market-agnostic order type ○ skipped defaults to market selectVisibleCandleCount ○ skipped returns the persisted count ○ skipped defaults to 30 selectPerpsMode ○ skipped returns the current mode ○ skipped returns the default mode when the state slice is missing ○ skipped returns the default mode when state is undefined Test Suites: 2 passed, 2 total Tests: 125 skipped, 10 passed, 135 total Snapshots: 0 total Time: 0.763 s, estimated 1 s Ran all test suites matching packages/perps-controller/tests/src/PerpsController.configuration.test.ts|packages/perps-controller/tests/src/selectors.test.ts with tests matching "argin mode". - PASS unit-tests-ok (assert_exit_code, 0ms): source=unit-tests, expected=0, actual=0 - PASS unit-tests-ran (assert_output, 1ms): source=unit-tests, stream=stderr, match=Tests: +\d+ skipped, 10 passed - PASS done (end, 0ms) ``` </details> ## 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) - [x] 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 (N/A: additive only) ## **Screenshots/Recordings** <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive persisted UI preference with input validation; no changes to order placement or margin enforcement on the venue. > > **Overview** > Adds **per-market, per-network persistence** for the Pro order form’s Isolated/Cross margin pick, aligned with existing `tradeConfigurations` entries for leverage and order-book grouping. > > Clients can store and restore the choice in `tradeConfigurations[network][symbol].marginMode` via **`getMarginMode` / `saveMarginMode`** (messenger actions `PerpsController:getMarginMode` and `PerpsController:saveMarginMode`) and **`selectMarginMode(state, symbol)`**. Saves merge into the existing trade config so leverage, grouping, and pending drafts are unchanged; only `'isolated'` and `'cross'` are accepted—other values are ignored. **`TradeConfiguration`** gains an optional `marginMode` field. **`placeOrder`** behavior is unchanged; venue locks still override the saved pick on the client. > > Changelog, exports, and unit tests cover controller methods, messenger registration, and the selector (mainnet vs testnet isolation). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 241c210. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
geositta
approved these changes
Sep 29, 2026
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
HyperLiquid binds the margin mode to the asset.
OrderParams.marginModeplacement is rejected when an open position (ORDER_MARGIN_MODE_POSITION_OPEN) or a resting order / active TWAP (ORDER_MARGIN_MODE_ORDER_OPEN) already fixes the other mode. Clients could not see that current mode:OrderandTwapOrdercarry no margin mode, andactiveAssetDatais only read inside#validateMarginMode. So a client picker could offer a mode that placement will refuse.This adds
PerpsController.getMarginModeLock({ symbol, providerId? })(plus thePerpsController:getMarginModeLockmessenger action) and the optionalPerpsProvider.getMarginModeLock. It returnslocked(withmarginModeandreason: 'position' | 'open_order'),unlocked, or a typedunavailableresult, and never throws.#validateMarginModeinto#readMarginModeLock, which both validation and the new method use, so the reported lock matches what placement enforces. Validation behavior is unchanged (same reads, same errors, same order).getOrderCapabilities(explicit/default provider,provider_not_found,provider_not_routable,not_implemented,provider_unavailable).UpdateLeveragecarries the wire mode; a missing field means cross). The existing position-row read moved into#readPositionMarginMode; the leverage-update path keeps its fallback behavior. Only open positions lock the mode on Lighter; resting orders are not treated as a lock.It also adds an optional
supportedMarginModesto ready order capabilities, so clients stop inferring margin-mode support from the provider (HyperLiquid, not HIP-3, not isolated-only). HyperLiquid reports['isolated', 'cross']for main-DEX markets and['isolated']for HIP-3 or isolated-only assets, from a single#isCrossMarginSupportedpredicate that#validateMarginModealso uses. Omitted means the provider does not report it and clients should not offer an explicit mode.References
Checklist
Note
Medium Risk
Touches HyperLiquid pre-sign margin validation and multi-step venue reads; behavior is intended to stay equivalent while exposing new client-facing state.
Overview
Clients can now query which margin mode a market is locked to (open position, resting order, or active TWAP) via
PerpsController.getMarginModeLockand the matching messenger action, with optionalPerpsProvider.getMarginModeLockon HyperLiquid and Lighter. Results arelocked/unlocked/ typedunavailableand never throw, using the same provider routing as order capabilities.HyperLiquid centralizes lock detection in
#readMarginModeLock(shared with pre-sign#validateMarginMode) and adds account pinning so position/order reads cannot mix accounts. Ready order capabilities now optionally includesupportedMarginModes; HyperLiquid derives isolated vs cross from#isCrossMarginSupported(main DEX vs HIP-3 / isolated-only metadata), so UIs need not guess from the provider id.Lighter locks only on open positions (resting orders ignored), reusing
#readPositionMarginModefor leverage updates and the new API. Aggregated routing and tests cover the new surface end-to-end.Reviewed by Cursor Bugbot for commit 1827be4. Bugbot is set up for automated code reviews on this repo. Configure here.