diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index c786d0c8f65a..b6987f8582ba 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -6013,6 +6013,9 @@
"perpsTrades": {
"message": "Trades"
},
+ "perpsTriggerPrice": {
+ "message": "Trigger price"
+ },
"perpsTutorialChooseLeverageDescription": {
"message": "Leverage amplifies both gains and losses. With 40x leverage, a 1% price move equals a 40% gain or loss on your margin."
},
diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json
index c786d0c8f65a..b6987f8582ba 100644
--- a/app/_locales/en_GB/messages.json
+++ b/app/_locales/en_GB/messages.json
@@ -6013,6 +6013,9 @@
"perpsTrades": {
"message": "Trades"
},
+ "perpsTriggerPrice": {
+ "message": "Trigger price"
+ },
"perpsTutorialChooseLeverageDescription": {
"message": "Leverage amplifies both gains and losses. With 40x leverage, a 1% price move equals a 40% gain or loss on your margin."
},
diff --git a/ui/components/app/perps/order-card/order-card.test.tsx b/ui/components/app/perps/order-card/order-card.test.tsx
index 23999c388aff..ba921f066b2d 100644
--- a/ui/components/app/perps/order-card/order-card.test.tsx
+++ b/ui/components/app/perps/order-card/order-card.test.tsx
@@ -50,25 +50,118 @@ describe('OrderCard', () => {
expect(screen.getByText('TSLA')).toBeInTheDocument();
});
- it('displays Long for buy side order', () => {
- const order = createMockOrder({ side: 'buy' });
- renderWithProvider(, mockStore);
+ describe('order label (formatOrderLabel)', () => {
+ it('displays "Limit long" for a plain buy limit order', () => {
+ const order = createMockOrder({ side: 'buy', orderType: 'limit' });
+ renderWithProvider(, mockStore);
- expect(screen.getByText(messages.perpsLong.message)).toBeInTheDocument();
- });
+ expect(screen.getByText('Limit long')).toBeInTheDocument();
+ });
- it('displays Short for sell side order', () => {
- const order = createMockOrder({ side: 'sell' });
- renderWithProvider(, mockStore);
+ it('displays "Limit short" for a plain sell limit order', () => {
+ const order = createMockOrder({ side: 'sell', orderType: 'limit' });
+ renderWithProvider(, mockStore);
- expect(screen.getByText(messages.perpsShort.message)).toBeInTheDocument();
- });
+ expect(screen.getByText('Limit short')).toBeInTheDocument();
+ });
- it('displays the order type', () => {
- const order = createMockOrder({ orderType: 'limit' });
- renderWithProvider(, mockStore);
+ it('displays "Market long" for a plain buy market order', () => {
+ const order = createMockOrder({
+ side: 'buy',
+ orderType: 'market',
+ price: '0',
+ });
+ renderWithProvider(, mockStore);
+
+ expect(screen.getByText('Market long')).toBeInTheDocument();
+ });
+
+ it('displays "Limit close long" for a reduce-only sell limit order', () => {
+ const order = createMockOrder({
+ side: 'sell',
+ orderType: 'limit',
+ reduceOnly: true,
+ });
+ renderWithProvider(, mockStore);
+
+ expect(screen.getByText('Limit close long')).toBeInTheDocument();
+ });
+
+ it('displays "Limit close short" for a reduce-only buy limit order', () => {
+ const order = createMockOrder({
+ side: 'buy',
+ orderType: 'limit',
+ reduceOnly: true,
+ });
+ renderWithProvider(, mockStore);
+
+ expect(screen.getByText('Limit close short')).toBeInTheDocument();
+ });
- expect(screen.getByText(messages.perpsLimit.message)).toBeInTheDocument();
+ it('displays "Take profit limit close long" for a TP trigger (sell side)', () => {
+ const order = createMockOrder({
+ side: 'sell',
+ orderType: 'limit',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Take Profit Limit',
+ });
+ renderWithProvider(, mockStore);
+
+ expect(
+ screen.getByText('Take profit limit close long'),
+ ).toBeInTheDocument();
+ });
+
+ it('shows market symbol only after size for TP/SL, not before the label', () => {
+ const order = createMockOrder({
+ side: 'sell',
+ orderType: 'limit',
+ symbol: 'ETH',
+ size: '1.5',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Take Profit Limit',
+ triggerPrice: '3200',
+ price: '3200',
+ });
+ renderWithProvider(, mockStore);
+
+ expect(
+ screen.getByText('Take profit limit close long'),
+ ).toBeInTheDocument();
+ expect(screen.getByText('1.5 ETH')).toBeInTheDocument();
+ expect(screen.queryByText(/^ETH$/u)).not.toBeInTheDocument();
+ });
+
+ it('shows "Trigger price" subtitle for TP/SL orders', () => {
+ const order = createMockOrder({
+ side: 'sell',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Take Profit Limit',
+ triggerPrice: '3200',
+ price: '3200',
+ });
+ renderWithProvider(, mockStore);
+
+ expect(
+ screen.getByText(messages.perpsTriggerPrice.message),
+ ).toBeInTheDocument();
+ });
+
+ it('displays "Stop market close short" for a SL trigger (buy side)', () => {
+ const order = createMockOrder({
+ side: 'buy',
+ orderType: 'market',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Stop Market',
+ });
+ renderWithProvider(, mockStore);
+
+ expect(screen.getByText('Stop market close short')).toBeInTheDocument();
+ });
});
it('displays the order size with symbol', () => {
@@ -90,6 +183,23 @@ describe('OrderCard', () => {
expect(screen.getByText('$3,500.00')).toBeInTheDocument();
});
+ it('displays TP/SL trigger price, not size × price notional', () => {
+ const order = createMockOrder({
+ orderType: 'limit',
+ side: 'sell',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Take Profit Limit',
+ triggerPrice: '3200.00',
+ price: '3200.00',
+ size: '2.0',
+ });
+ renderWithProvider(, mockStore);
+
+ expect(screen.getByText('$3,200.00')).toBeInTheDocument();
+ expect(screen.queryByText('$6,400.00')).not.toBeInTheDocument();
+ });
+
it('displays Market label when order value is zero', () => {
const order = createMockOrder({
orderType: 'market',
@@ -98,9 +208,7 @@ describe('OrderCard', () => {
});
renderWithProvider(, mockStore);
- // "Market" appears in both the value slot and the order type slot
- const marketElements = screen.getAllByText(messages.perpsMarket.message);
- expect(marketElements.length).toBeGreaterThanOrEqual(1);
+ expect(screen.getByText(messages.perpsMarket.message)).toBeInTheDocument();
});
it('renders the token logo', () => {
diff --git a/ui/components/app/perps/order-card/order-card.tsx b/ui/components/app/perps/order-card/order-card.tsx
index e41d17eaa473..0e5c13f7e04c 100644
--- a/ui/components/app/perps/order-card/order-card.tsx
+++ b/ui/components/app/perps/order-card/order-card.tsx
@@ -15,7 +15,8 @@ import { useNavigate } from 'react-router-dom';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { useFormatters } from '../../../../hooks/useFormatters';
import { PerpsTokenLogo } from '../perps-token-logo';
-import { formatOrderType, getDisplayName } from '../utils';
+import { getDisplayName } from '../utils';
+import { formatOrderLabel } from '../utils/orderUtils';
import type { Order } from '../types';
import { PERPS_MARKET_DETAIL_ROUTE } from '../../../../helpers/constants/routes';
@@ -27,7 +28,8 @@ export type OrderCardProps = {
/**
* OrderCard component displays individual order information
- * Two rows: symbol/type/side + size on left, USD value + limit price on right
+ * Two rows on the left: symbol + order label (TP/SL: label only; symbol follows size below),
+ * trigger or notional value on the right
*
* @param options0 - Component props
* @param options0.order - The order data to display
@@ -42,8 +44,9 @@ export const OrderCard: React.FC = ({
const navigate = useNavigate();
const t = useI18nContext();
const { formatCurrencyWithMinThreshold } = useFormatters();
- const isBuy = order.side === 'buy';
const displayName = getDisplayName(order.symbol);
+ const isTriggerBasedOrder =
+ order.isTrigger === true || order.isPositionTpsl === true;
const handleClick = useCallback(() => {
if (onClick) {
@@ -56,17 +59,34 @@ export const OrderCard: React.FC = ({
}
}, [navigate, order, onClick]);
- // Calculate order value in USD (size * price), formatted like position values
+ // Limit/market: notional (size × limit price). TP/SL: trigger level (take-profit / stop-loss price).
const orderValueUsd = useMemo(() => {
+ if (isTriggerBasedOrder) {
+ const triggerLevel =
+ parseFloat(order.triggerPrice || order.price || '0') || 0;
+ if (triggerLevel > 0) {
+ return formatCurrencyWithMinThreshold(triggerLevel, 'USD');
+ }
+ }
+
const size = parseFloat(order.size) || 0;
const price = parseFloat(order.price) || 0;
if (size > 0 && price > 0) {
return formatCurrencyWithMinThreshold(size * price, 'USD');
}
return null;
- }, [order.size, order.price, formatCurrencyWithMinThreshold]);
+ }, [
+ isTriggerBasedOrder,
+ order.triggerPrice,
+ order.size,
+ order.price,
+ formatCurrencyWithMinThreshold,
+ ]);
- const baseStyles = 'cursor-pointer pt-2 pb-2 px-4 h-[62px]';
+ const baseStyles = 'cursor-pointer pt-2 pb-2 px-4';
+ // Non-trigger rows keep the fixed 62 px height to match the position/token tabs.
+ // Trigger-based (TP/SL) rows grow with content; min-h keeps the floor at 62 px.
+ const heightStyle = isTriggerBasedOrder ? 'h-auto min-h-[62px]' : 'h-[62px]';
const variantStyles =
variant === 'muted'
? 'bg-muted hover:bg-muted-hover active:bg-muted-pressed'
@@ -77,9 +97,11 @@ export const OrderCard: React.FC = ({
className={twMerge(
// Reset ButtonBase defaults for card layout
'justify-start rounded-none min-w-0',
- // Card styles (matches tokens tab: 62px height, 8px v-padding, 16px h-padding, 16px gap)
- 'gap-4 text-left',
+ // items-center keeps each column's content block centered in the card height,
+ // whether the label fits on one line or wraps to two.
+ 'gap-4 text-left items-center',
baseStyles,
+ heightStyle,
variantStyles,
)}
isFullWidth
@@ -100,22 +122,31 @@ export const OrderCard: React.FC = ({
alignItems={BoxAlignItems.Start}
gap={1}
>
-
- {displayName}
-
- {isBuy ? t('perpsLong') : t('perpsShort')}
-
-
+ {isTriggerBasedOrder ? (
+ // TP/SL: render label directly in the column so it wraps freely.
+ // The symbol is redundant here — it appears after the size below.
+ {formatOrderLabel(order)}
+ ) : (
+
+ {displayName}
+
+ {formatOrderLabel(order)}
+
+
+ )}
{order.size} {displayName}
- {/* Right side: USD value + limit price */}
+ {/* Right side: USD value */}
= ({
{orderValueUsd ?? t('perpsMarket')}
-
- {formatOrderType(order.orderType)}
-
+ {isTriggerBasedOrder && orderValueUsd && (
+
+ {t('perpsTriggerPrice')}
+
+ )}
);
diff --git a/ui/components/app/perps/perps-view.test.tsx b/ui/components/app/perps/perps-view.test.tsx
index 4ad1807b3ff0..6cf50ca88ef0 100644
--- a/ui/components/app/perps/perps-view.test.tsx
+++ b/ui/components/app/perps/perps-view.test.tsx
@@ -177,6 +177,15 @@ describe('PerpsView', () => {
expect(screen.getByTestId('order-card-order-001')).toBeInTheDocument();
});
+ it('filters TP/SL trigger orders from Open orders on the Perps tab', () => {
+ renderWithProvider(, mockStore);
+
+ expect(screen.getByTestId('order-card-order-001')).toBeInTheDocument();
+ expect(
+ screen.queryByTestId('order-card-order-004'),
+ ).not.toBeInTheDocument();
+ });
+
it('displays position section header', () => {
renderWithProvider(, mockStore);
diff --git a/ui/components/app/perps/perps-view.tsx b/ui/components/app/perps/perps-view.tsx
index d675189b1ba1..4f1946f66b04 100644
--- a/ui/components/app/perps/perps-view.tsx
+++ b/ui/components/app/perps/perps-view.tsx
@@ -93,8 +93,9 @@ export const PerpsView: React.FC = () => {
} = usePerpsTransactionHistory();
// Recent Activity shows only trade executions, deposits, and withdrawals.
- // Open orders are already surfaced in PerpsPositionsOrders above.
- // Funding payments belong in the full activity page.
+ // Open limit/market orders (excluding TP/SL triggers) are in PerpsPositionsOrders;
+ // TP/SL trigger rows are listed on the per-asset market detail page only.
+ // Funding payments belong on the full activity page.
const recentActivityTransactions = useMemo(
() =>
allRecentActivityTransactions.filter(
@@ -106,14 +107,15 @@ export const PerpsView: React.FC = () => {
[allRecentActivityTransactions],
);
- // Show only user-placed limit orders resting on the orderbook.
- // Excludes:
- // - isTrigger: TP/SL trigger orders
- // - isSynthetic: synthetic/virtual orders not placed directly by the user
+ // Open orders on the Perps tab: user-placed limits/markets on the book only.
+ // Excludes TP/SL (isTrigger / isPositionTpsl — those list on market detail) and synthetics.
const orders = useMemo(() => {
return allOrders.filter(
(order) =>
- order.status === 'open' && !order.isTrigger && !order.isSynthetic,
+ order.status === 'open' &&
+ !order.isTrigger &&
+ order.isPositionTpsl !== true &&
+ !order.isSynthetic,
);
}, [allOrders]);
diff --git a/ui/components/app/perps/utils/index.ts b/ui/components/app/perps/utils/index.ts
index 6a93fba07f32..c223c8b552e8 100644
--- a/ui/components/app/perps/utils/index.ts
+++ b/ui/components/app/perps/utils/index.ts
@@ -21,6 +21,7 @@ export {
shouldDisplayOrderInMarketDetailsOrders,
buildDisplayOrdersWithSyntheticTpsl,
isOrderAssociatedWithFullPosition,
+ formatOrderLabel,
} from './orderUtils';
export { formatPerpsPrice, type PerpsPriceRange } from './formatPerpsPrice';
diff --git a/ui/components/app/perps/utils/orderUtils.test.ts b/ui/components/app/perps/utils/orderUtils.test.ts
index 9a1d04921482..a56522ccb4f0 100644
--- a/ui/components/app/perps/utils/orderUtils.test.ts
+++ b/ui/components/app/perps/utils/orderUtils.test.ts
@@ -4,6 +4,7 @@ import {
shouldDisplayOrderInMarketDetailsOrders,
buildDisplayOrdersWithSyntheticTpsl,
normalizeMarketDetailsOrders,
+ formatOrderLabel,
} from './orderUtils';
const makeOrder = (overrides: Partial = {}): Order => ({
@@ -115,27 +116,24 @@ describe('orderUtils', () => {
});
describe('shouldDisplayOrderInMarketDetailsOrders', () => {
- it('shows non-reduce-only orders', () => {
- const order = makeOrder({ reduceOnly: false });
- expect(shouldDisplayOrderInMarketDetailsOrders(order)).toBe(true);
- });
-
- it('shows limit orders', () => {
- const order = makeOrder({ orderType: 'limit', reduceOnly: false });
- expect(shouldDisplayOrderInMarketDetailsOrders(order)).toBe(true);
+ it('includes non-reduce-only and limit orders', () => {
+ expect(shouldDisplayOrderInMarketDetailsOrders(makeOrder())).toBe(true);
+ expect(
+ shouldDisplayOrderInMarketDetailsOrders(
+ makeOrder({ orderType: 'limit', reduceOnly: false }),
+ ),
+ ).toBe(true);
});
- it('shows trigger orders that are not position-attached', () => {
- const order = makeOrder({
+ it('includes trigger orders and partial reduce-only closes', () => {
+ const triggerOrder = makeOrder({
isTrigger: true,
reduceOnly: false,
triggerPrice: '3200.00',
});
- expect(shouldDisplayOrderInMarketDetailsOrders(order)).toBe(true);
- });
+ expect(shouldDisplayOrderInMarketDetailsOrders(triggerOrder)).toBe(true);
- it('shows reduce-only orders not associated with full position', () => {
- const order = makeOrder({
+ const partialClose = makeOrder({
reduceOnly: true,
symbol: 'ETH',
side: 'sell',
@@ -143,13 +141,13 @@ describe('orderUtils', () => {
originalSize: '0.5',
});
const position = makePosition({ symbol: 'ETH', size: '1.0' });
- expect(shouldDisplayOrderInMarketDetailsOrders(order, position)).toBe(
- true,
- );
+ expect(
+ shouldDisplayOrderInMarketDetailsOrders(partialClose, position),
+ ).toBe(true);
});
- it('hides reduce-only orders associated with full position', () => {
- const order = makeOrder({
+ it('includes full-position reduce-only and isPositionTpsl orders', () => {
+ const fullClose = makeOrder({
reduceOnly: true,
symbol: 'ETH',
side: 'sell',
@@ -157,17 +155,15 @@ describe('orderUtils', () => {
originalSize: '1.0',
});
const position = makePosition({ symbol: 'ETH', size: '1.0' });
- expect(shouldDisplayOrderInMarketDetailsOrders(order, position)).toBe(
- false,
+ expect(shouldDisplayOrderInMarketDetailsOrders(fullClose, position)).toBe(
+ true,
);
- });
- it('hides orders with isPositionTpsl true', () => {
- const order = makeOrder({
+ const positionTpsl = makeOrder({
reduceOnly: true,
isPositionTpsl: true,
});
- expect(shouldDisplayOrderInMarketDetailsOrders(order)).toBe(false);
+ expect(shouldDisplayOrderInMarketDetailsOrders(positionTpsl)).toBe(true);
});
});
@@ -289,7 +285,7 @@ describe('orderUtils', () => {
expect(result).toHaveLength(1);
});
- it('hides full-position TP/SL reduce-only orders', () => {
+ it('includes full-position TP/SL reduce-only orders', () => {
const tpslOrder = makeOrder({
reduceOnly: true,
isPositionTpsl: true,
@@ -298,7 +294,8 @@ describe('orderUtils', () => {
detailedOrderType: 'Take Profit Limit',
});
const result = normalizeMarketDetailsOrders({ orders: [tpslOrder] });
- expect(result).toHaveLength(0);
+ expect(result).toHaveLength(1);
+ expect(result[0].orderId).toBe('order-1');
});
it('shows partial-close reduce-only orders', () => {
@@ -317,7 +314,7 @@ describe('orderUtils', () => {
expect(result).toHaveLength(1);
});
- it('adds synthetic TP/SL rows and filters them through position check', () => {
+ it('adds synthetic TP/SL rows and keeps them in the list', () => {
const limitOrder = makeOrder({
orderType: 'limit',
reduceOnly: false,
@@ -330,4 +327,116 @@ describe('orderUtils', () => {
expect(result).toHaveLength(3);
});
});
+
+ describe('formatOrderLabel', () => {
+ it('returns "Limit long" for a plain buy limit order', () => {
+ const order = makeOrder({ side: 'buy', orderType: 'limit' });
+ expect(formatOrderLabel(order)).toBe('Limit long');
+ });
+
+ it('returns "Limit short" for a plain sell limit order', () => {
+ const order = makeOrder({ side: 'sell', orderType: 'limit' });
+ expect(formatOrderLabel(order)).toBe('Limit short');
+ });
+
+ it('returns "Market long" for a plain buy market order', () => {
+ const order = makeOrder({ side: 'buy', orderType: 'market' });
+ expect(formatOrderLabel(order)).toBe('Market long');
+ });
+
+ it('returns "Market short" for a plain sell market order', () => {
+ const order = makeOrder({ side: 'sell', orderType: 'market' });
+ expect(formatOrderLabel(order)).toBe('Market short');
+ });
+
+ it('returns "Limit close long" for a reduce-only sell limit order', () => {
+ const order = makeOrder({
+ side: 'sell',
+ orderType: 'limit',
+ reduceOnly: true,
+ });
+ expect(formatOrderLabel(order)).toBe('Limit close long');
+ });
+
+ it('returns "Limit close short" for a reduce-only buy limit order', () => {
+ const order = makeOrder({
+ side: 'buy',
+ orderType: 'limit',
+ reduceOnly: true,
+ });
+ expect(formatOrderLabel(order)).toBe('Limit close short');
+ });
+
+ it('returns "Market close long" for a reduce-only sell market order', () => {
+ const order = makeOrder({
+ side: 'sell',
+ orderType: 'market',
+ reduceOnly: true,
+ });
+ expect(formatOrderLabel(order)).toBe('Market close long');
+ });
+
+ it('returns "Market close short" for a reduce-only buy market order', () => {
+ const order = makeOrder({
+ side: 'buy',
+ orderType: 'market',
+ reduceOnly: true,
+ });
+ expect(formatOrderLabel(order)).toBe('Market close short');
+ });
+
+ it('returns "Take profit limit close long" for a TP trigger (sell)', () => {
+ const order = makeOrder({
+ side: 'sell',
+ orderType: 'limit',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Take Profit Limit',
+ });
+ expect(formatOrderLabel(order)).toBe('Take profit limit close long');
+ });
+
+ it('returns "Take profit market close short" for a TP trigger (buy)', () => {
+ const order = makeOrder({
+ side: 'buy',
+ orderType: 'market',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Take Profit Market',
+ });
+ expect(formatOrderLabel(order)).toBe('Take profit market close short');
+ });
+
+ it('returns "Stop limit close long" for a SL trigger (sell)', () => {
+ const order = makeOrder({
+ side: 'sell',
+ orderType: 'limit',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Stop Limit',
+ });
+ expect(formatOrderLabel(order)).toBe('Stop limit close long');
+ });
+
+ it('returns "Stop market close short" for a SL trigger (buy)', () => {
+ const order = makeOrder({
+ side: 'buy',
+ orderType: 'market',
+ isTrigger: true,
+ reduceOnly: true,
+ detailedOrderType: 'Stop Market',
+ });
+ expect(formatOrderLabel(order)).toBe('Stop market close short');
+ });
+
+ it('treats isTrigger alone as closing (no reduceOnly)', () => {
+ const order = makeOrder({
+ side: 'sell',
+ orderType: 'market',
+ isTrigger: true,
+ detailedOrderType: 'Stop Market',
+ });
+ expect(formatOrderLabel(order)).toBe('Stop market close long');
+ });
+ });
});
diff --git a/ui/components/app/perps/utils/orderUtils.ts b/ui/components/app/perps/utils/orderUtils.ts
index d993ae6f9812..048c031d6b4f 100644
--- a/ui/components/app/perps/utils/orderUtils.ts
+++ b/ui/components/app/perps/utils/orderUtils.ts
@@ -1,4 +1,5 @@
import BigNumber from 'bignumber.js';
+import { capitalize } from 'lodash';
import type { Order, Position } from '@metamask/perps-controller';
const FULL_POSITION_SIZE_TOLERANCE = new BigNumber('0.00000001');
@@ -166,24 +167,21 @@ export const isOrderAssociatedWithFullPosition = (
};
/**
- * Determines whether an order should be shown in Market Details > Orders.
+ * Determines whether an order should be shown in Market Details > Orders
+ * (the perps asset / position detail screen).
*
- * - All non-reduce-only orders are shown.
- * - Reduce-only orders are shown only when they are NOT full-position TP/SL.
+ * Full-position TP/SL and `isPositionTpsl` rows are included here so users can
+ * see and act on them alongside other open orders, in addition to the position
+ * summary. `isOrderAssociatedWithFullPosition` remains for other call sites.
*
- * @param order - The order to check
- * @param position - The current position (if any)
+ * @param _order - The order to check (reserved for future filtering)
+ * @param _position - The current position (reserved for future filtering)
* @returns Whether the order should be displayed
*/
export const shouldDisplayOrderInMarketDetailsOrders = (
- order: Order,
- position?: Position,
-): boolean => {
- if (!order.reduceOnly) {
- return true;
- }
- return !isOrderAssociatedWithFullPosition(order, position);
-};
+ _order: Order,
+ _position?: Position,
+): boolean => true;
const buildSyntheticTriggerOrder = (
parentOrder: Order,
@@ -305,7 +303,7 @@ export const buildDisplayOrdersWithSyntheticTpsl = (
* Normalizes orders for the Perps Market Details > Orders section.
*
* Composes display-order enrichment (synthetic TP/SL rows) with visibility
- * filtering (hides full-position TP/SL, keeps everything else).
+ * filtering (currently all enriched orders are shown, including full-position TP/SL).
*
* @param options0 - Options object
* @param options0.orders - The orders to normalize
@@ -325,3 +323,41 @@ export const normalizeMarketDetailsOrders = ({
shouldDisplayOrderInMarketDetailsOrders(order, existingPosition),
);
};
+
+/**
+ * Formats an order label following the pattern: [Type] [close?] [direction]
+ * Matches the mobile canonical formatter in app/components/UI/Perps/utils/orderUtils.ts.
+ *
+ * Examples:
+ * - "Market long" / "Limit short"
+ * - "Limit close long" / "Market close short"
+ * - "Stop market close long" / "Take profit limit close short"
+ *
+ * Rules:
+ * - isClosing = reduceOnly || isTrigger
+ * - direction for closing: sell → long, buy → short
+ * - direction for opening: buy → long, sell → short
+ * - typeString = detailedOrderType if present, otherwise "Limit" or "Market"
+ * - lodash capitalize: uppercases first char, lowercases the rest
+ *
+ * @param order - The order to label
+ * @returns Formatted label string in sentence case
+ */
+export const formatOrderLabel = (order: Order): string => {
+ const { side, detailedOrderType, orderType, reduceOnly, isTrigger } = order;
+ const isClosing = Boolean(reduceOnly || isTrigger);
+
+ let direction: string;
+ if (isClosing) {
+ direction = side === 'sell' ? 'long' : 'short';
+ } else {
+ direction = side === 'buy' ? 'long' : 'short';
+ }
+
+ const typeString =
+ detailedOrderType || (orderType === 'limit' ? 'Limit' : 'Market');
+
+ return isClosing
+ ? capitalize(`${typeString} close ${direction}`)
+ : capitalize(`${typeString} ${direction}`);
+};
diff --git a/ui/components/app/perps/utils/transactionTransforms.ts b/ui/components/app/perps/utils/transactionTransforms.ts
index a0170f00016c..d3e4bf0ffe27 100644
--- a/ui/components/app/perps/utils/transactionTransforms.ts
+++ b/ui/components/app/perps/utils/transactionTransforms.ts
@@ -21,6 +21,7 @@ import {
type PerpsTransaction,
} from '../types/transactionHistory';
import { getDisplaySymbol } from '../utils';
+import { formatOrderLabel } from './orderUtils';
/**
* Determines the close direction category for aggregation purposes.
@@ -192,53 +193,6 @@ export function aggregateFillsByTimestamp(fills: OrderFill[]): OrderFill[] {
return allFills;
}
-/**
- * Format an order label following the pattern: [Type] [Close?] [Direction]
- *
- * Examples:
- * - Market Long
- * - Market Close Long
- * - Limit Short
- * - Limit Close Short
- * - Stop Market Close Long
- * - Take Profit Limit Close Short
- *
- * @param order - The order object
- * @returns Formatted order label string
- */
-function formatOrderLabel(order: Order): string {
- const { side, detailedOrderType, orderType, reduceOnly, isTrigger } = order;
-
- // Determine if this is a closing order
- const isClosing = Boolean(reduceOnly || isTrigger);
-
- // Determine direction based on whether it's closing or not
- let direction: string;
- if (isClosing) {
- // For closing orders: sell closes long, buy closes short
- direction = side === 'sell' ? 'long' : 'short';
- } else {
- // For opening orders: buy is long, sell is short
- direction = side === 'buy' ? 'long' : 'short';
- }
-
- // Get the order type string
- // Use detailedOrderType if available (e.g., "Stop Market", "Take Profit Limit")
- // Otherwise fall back to basic orderType
- const typeString =
- detailedOrderType || (orderType === 'limit' ? 'Limit' : 'Market');
-
- // Build the label: [Type] [Close?] [Direction]
- // Capitalize first letter
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
-
- if (isClosing) {
- return capitalize(`${typeString} close ${direction}`);
- }
-
- return capitalize(`${typeString} ${direction}`);
-}
-
/**
* Transform abstract OrderFill objects to PerpsTransaction format.
* Close fills that occur at the same timestamp for the same asset are automatically
diff --git a/ui/pages/perps/perps-market-detail-page.tsx b/ui/pages/perps/perps-market-detail-page.tsx
index 7c42be962ff5..d6ca74de73cf 100644
--- a/ui/pages/perps/perps-market-detail-page.tsx
+++ b/ui/pages/perps/perps-market-detail-page.tsx
@@ -457,8 +457,8 @@ const PerpsMarketDetailPage: React.FC = () => {
}, [position]);
// Filter and sort open orders for this market, then normalize for display.
- // Normalization adds synthetic TP/SL rows for parent orders and filters out
- // full-position TP/SL (those stay on the position card / auto-close section).
+ // Normalization adds synthetic TP/SL rows for parent orders when no matching
+ // real trigger exists; full-position TP/SL also appear in this Orders list.
const orders = useMemo(() => {
if (!decodedSymbol) {
return [];