Skip to content

Commit e04593c

Browse files
chore(runway): cherry-pick fix: fix marging value on OrderView from tokenDetails page cp-7.66.0 (#26154)
- fix: fix marging value on OrderView from tokenDetails page (#26105) ## **Description** When navigating to the Perps order view from the **Token Details** page (Long/Short buttons), the margin value at the bottom of the order screen is permanently stuck at **$0**, regardless of the amount entered. This does not happen when entering from the Perps main flow (Market Details → Long/Short), because an earlier screen (`PerpsMarketStatisticsCard`) subscribes to price data with `includeMarketData: true`, which populates the `HyperLiquidSubscriptionService`'s `#marketDataCache` with oracle prices. ### Root cause The `marginRequired` calculation in `PerpsOrderView` uses `assetData.markPrice` (oracle price). This value comes from `usePerpsLivePrices` → `PriceStreamChannel` → `PerpsController.subscribeToPrices()`. However, the `PriceStreamChannel` **never passes `includeMarketData: true`** in its subscription. Without this flag, the `activeAssetCtx` WebSocket subscription is not created, the `#marketDataCache` stays empty, and `markPrice` is always `undefined` — resulting in a `$0` margin. When the user visits the Perps Market Details page first, `usePerpsMarketStats` subscribes with `includeMarketData: true`, which populates the shared singleton cache. Subsequent price updates from `allMids` then include `markPrice` from the cache. This is why the bug only manifests on a fresh app start when going directly from Token Details. ### Fix Added a `useEffect` in `PerpsOrderViewContentBase` that subscribes to `PerpsController.subscribeToPrices` with `includeMarketData: true` for the traded asset. This creates an `activeAssetCtx` WebSocket subscription that populates the `#marketDataCache` with the oracle price. The existing `usePerpsLivePrices` subscription then picks up `markPrice` from the cache on the next `allMids` update. This mirrors what `usePerpsMarketStats` already does on the Market Details page. ## **Changelog** CHANGELOG entry: Fixed perpetual trading margin display showing $0 when placing orders from the Token Details page ## **Related issues** Fixes: #26106 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/30213d59-b131-4459-8b74-d37f7f0e152f ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/c0fb6543-1744-46cf-9f92-128f8118cb2b ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, localized change that adds a single price subscription gated by controller initialization; main risk is unintended extra websocket subscription or missing cleanup, but it returns the provided unsubscribe. > > **Overview** > Fixes a Perps `OrderView` bug where margin stayed at `$0` when arriving from Token Details by adding a gated `useEffect` that calls `subscribeToPrices({ includeMarketData: true })` for the selected asset, ensuring `markPrice`/oracle price is available for margin calculation. > > Updates tests to mock the new direct `usePerpsConnection` import and the added `usePerpsTrading.subscribeToPrices` function so the view can mount cleanly under Jest. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 90b90ae. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [786871d](786871d) Co-authored-by: sahar-fehri <sahar.fehri@consensys.net>
1 parent e0388eb commit e04593c

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,18 @@ jest.mock('../../hooks', () => ({
334334
}));
335335

336336
// Mock direct hook imports (when imported from specific file paths)
337+
jest.mock('../../hooks/usePerpsConnection', () => ({
338+
usePerpsConnection: jest.fn(() => ({
339+
isConnected: true,
340+
isConnecting: false,
341+
isInitialized: true,
342+
error: null,
343+
connect: jest.fn(),
344+
disconnect: jest.fn(),
345+
resetError: jest.fn(),
346+
})),
347+
}));
348+
337349
jest.mock('../../hooks/usePerpsPaymentTokens', () => ({
338350
usePerpsPaymentTokens: jest.fn(() => [
339351
{
@@ -679,6 +691,7 @@ const defaultMockHooks = {
679691
placeOrder: jest.fn(),
680692
depositWithConfirmation: jest.fn().mockResolvedValue(undefined),
681693
withdrawWithConfirmation: jest.fn(),
694+
subscribeToPrices: jest.fn(() => jest.fn()),
682695
getMarkets: jest.fn().mockResolvedValue([
683696
{
684697
name: 'ETH',

app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import {
119119
usePerpsLivePrices,
120120
usePerpsTopOfBook,
121121
} from '../../hooks/stream';
122+
import { usePerpsConnection } from '../../hooks/usePerpsConnection';
122123
import { useIsPerpsBalanceSelected } from '../../hooks/useIsPerpsBalanceSelected';
123124
import { usePerpsEventTracking } from '../../hooks/usePerpsEventTracking';
124125
import { usePerpsMeasurement } from '../../hooks/usePerpsMeasurement';
@@ -256,6 +257,8 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
256257
const orderStartTimeRef = useRef<number>(0);
257258
const inputMethodRef = useRef<InputMethod>('default');
258259

260+
const { isInitialized } = usePerpsConnection();
261+
const { subscribeToPrices, updatePositionTPSL } = usePerpsTrading();
259262
const { account, isInitialLoading: isLoadingAccount } = usePerpsLiveAccount();
260263

261264
// Get order form state from context; balanceForValidation respects custom token amount when set
@@ -372,6 +375,32 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
372375
},
373376
});
374377

378+
// Ensure oracle price (markPrice) is available for margin calculation.
379+
// The PriceStreamChannel subscribes without includeMarketData, so the
380+
// HyperLiquid marketDataCache has no oraclePrice and markPrice is always
381+
// undefined when entering from Token Details (no prior market data sub).
382+
// This subscription populates the cache for the traded asset so that
383+
// subsequent allMids price updates include markPrice.
384+
//
385+
// We gate on isInitialized (from usePerpsConnection) so the subscription
386+
// is deferred until the controller is ready. This mirrors the pattern in
387+
// usePerpsPrices and avoids a silent failure that would leave markPrice
388+
// undefined with no retry (the old deps would never change).
389+
useEffect(() => {
390+
if (!isDataReady || !isInitialized || !orderForm.asset) return;
391+
392+
const unsubscribe = subscribeToPrices({
393+
symbols: [orderForm.asset],
394+
includeMarketData: true,
395+
callback: () => {
396+
// No-op callback — the side effect of populating the
397+
// marketDataCache with oraclePrice is all we need.
398+
},
399+
});
400+
401+
return unsubscribe;
402+
}, [isDataReady, isInitialized, orderForm.asset, subscribeToPrices]);
403+
375404
// Get real-time price data using new stream architecture (deferred)
376405
// Uses single WebSocket subscription with component-level debouncing
377406
const prices = usePerpsLivePrices({
@@ -524,8 +553,6 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
524553
return requiredUsd > balanceUsd;
525554
}, [hasCustomTokenSelected, marginRequired, payToken]);
526555

527-
const { updatePositionTPSL } = usePerpsTrading();
528-
529556
// Order execution using new hook
530557
const { placeOrder: executeOrder, isPlacing: isPlacingOrder } =
531558
usePerpsOrderExecution({

0 commit comments

Comments
 (0)