Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ For more on these commands, see:
### Linting and formatting

- Run `yarn lint` to check for code quality issues across the monorepo.
- Run `yarn validate:changelog` to check for formatting issues in changelogs.
- Run `yarn changelog:validate` to check for formatting issues in changelogs.
- Run `yarn lint:fix` to automatically fix fixable violations.

### Building packages
Expand Down Expand Up @@ -158,7 +158,7 @@ Each consumer-facing change to a package should be accompanied by one or more en
- Do not simply reuse the PR title in the entry, but describe exact changes to the API or usable surface area of the project.
- When there are multiple upgrades to a package in the same release, combine them into a single entry.
- Each changelog entry should describe one kind of change; if an entry describes too many things, split it up.
- After updating a changelog, run `yarn validate:changelog` and fix any errors reported.
- After updating a changelog, run `yarn changelog:validate` and fix any errors reported.

## Creating releases

Expand Down
5 changes: 5 additions & 0 deletions packages/perps-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Include `direction` on pending trade configurations so a 30-second draft restores long/short with size ([#9992](https://github.com/MetaMask/core/pull/9992))

### Fixed

- Preserve trigger prices and normalized trigger order types in HyperLiquid historical orders while retaining their lifecycle and execution semantics ([#9982](https://github.com/MetaMask/core/pull/9982)).
- Classify `xyz:CBRS` and `xyz:SPCX` as stocks in the Hyperliquid fallback market map ([#9988](https://github.com/MetaMask/core/pull/9988))

## [13.0.0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ export type PerpsControllerSaveTradeConfigurationAction = {
* @param config.limitPrice - The limit price.
* @param config.orderType - The order type.
* @param config.reduceOnly - Whether the order may only reduce a position.
* @param config.direction - Long or short.
* @param config.selectedPaymentToken - The selected payment token.
*/
export type PerpsControllerSavePendingTradeConfigurationAction = {
Expand Down
6 changes: 6 additions & 0 deletions packages/perps-controller/src/PerpsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import type {
MarketInfo,
Order,
OrderCapabilitiesUnavailableReason,
OrderDirection,
OrderFill,
OrderParams,
OrderResult,
Expand Down Expand Up @@ -456,6 +457,7 @@ export type PerpsControllerState = {
limitPrice?: string; // Limit price (for limit orders)
orderType?: OrderType; // Market vs limit
reduceOnly?: boolean; // Whether the order may only reduce a position
direction?: OrderDirection; // Long vs short
timestamp: number; // When the config was saved (for expiration check)
};
};
Expand All @@ -473,6 +475,7 @@ export type PerpsControllerState = {
limitPrice?: string; // Limit price (for limit orders)
orderType?: OrderType; // Market vs limit
reduceOnly?: boolean; // Whether the order may only reduce a position
direction?: OrderDirection; // Long vs short
timestamp: number; // When the config was saved (for expiration check)
};
};
Expand Down Expand Up @@ -6049,6 +6052,7 @@ export class PerpsController extends BaseController<
* @param config.limitPrice - The limit price.
* @param config.orderType - The order type.
* @param config.reduceOnly - Whether the order may only reduce a position.
* @param config.direction - Long or short.
* @param config.selectedPaymentToken - The selected payment token.
*/
savePendingTradeConfiguration(
Expand All @@ -6061,6 +6065,7 @@ export class PerpsController extends BaseController<
limitPrice?: string;
orderType?: OrderType;
reduceOnly?: boolean;
direction?: OrderDirection;
/** When user used pay-with-token in PerpsPayRow: minimal token shape to restore selection */
selectedPaymentToken?: PerpsSelectedPaymentToken | null;
},
Expand Down Expand Up @@ -6109,6 +6114,7 @@ export class PerpsController extends BaseController<
limitPrice?: string;
orderType?: OrderType;
reduceOnly?: boolean;
direction?: OrderDirection;
selectedPaymentToken?: PerpsSelectedPaymentToken | null;
}
| undefined {
Expand Down
43 changes: 23 additions & 20 deletions packages/perps-controller/src/providers/HyperLiquidProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
USDC_DECIMALS,
USDC_SYMBOL,
} from '../constants/hyperLiquidConfig.js';
import { DETAILED_ORDER_TYPES } from '../constants/orderTypes.js';
import {
CHASE_ORDER_CONFIG,
CHASE_ORDER_STATUS,
Expand Down Expand Up @@ -59,6 +60,7 @@ import {
} from '../services/TradingReadinessCache.js';
import type {
FrontendOrder,
OrderType as HyperLiquidOrderType,
SDKOrderParams,
MetaResponse,
PerpsAssetCtx,
Expand Down Expand Up @@ -224,6 +226,15 @@ import {
import { parseBoundedNonNegativeDecimal } from '../utils/stringParseUtils.js';
// getStreamManagerInstance removed: use this.#deps.streamManager instead

const HISTORICAL_ORDER_TYPE_BY_DETAILED_TYPE = {
[DETAILED_ORDER_TYPES.LIMIT]: 'limit',
[DETAILED_ORDER_TYPES.MARKET]: 'market',
[DETAILED_ORDER_TYPES.STOP_LIMIT]: 'limit',
[DETAILED_ORDER_TYPES.STOP_MARKET]: 'market',
[DETAILED_ORDER_TYPES.TAKE_PROFIT_LIMIT]: 'limit',
[DETAILED_ORDER_TYPES.TAKE_PROFIT_MARKET]: 'market',
} as const satisfies Record<HyperLiquidOrderType, Order['orderType']>;

/**
* Type guard to check if a status is an object (not a string literal like "waitingForFill")
* The SDK returns status as a union of object types and string literals.
Expand Down Expand Up @@ -10493,8 +10504,6 @@ export class HyperLiquidProvider implements PerpsProvider {
// Transform HyperLiquid orders to abstract Order type
const orders: Order[] = (rawOrders || []).map((rawOrder) => {
const { order, status, statusTimestamp } = rawOrder;
// Normalize side: HyperLiquid uses 'A' (Ask/Sell) and 'B' (Bid/Buy)
const normalizedSide = order.side === 'B' ? 'buy' : 'sell';

// Normalize status
let normalizedStatus: Order['status'];
Expand Down Expand Up @@ -10529,29 +10538,23 @@ export class HyperLiquidProvider implements PerpsProvider {
normalizedStatus = 'queued';
}

// Calculate filled and remaining size
const originalSize = parseFloat(order.origSz || order.sz);
const currentSize = parseFloat(order.sz);
const filledSize = originalSize - currentSize;
const adaptedOrder = adaptOrderFromSDK(order, undefined);
// limitPx is also populated as a slippage cap for market orders, so the
// exchange's detailed type is the reliable execution-mode source.
const historicalOrderType = hasProperty(
HISTORICAL_ORDER_TYPE_BY_DETAILED_TYPE,
order.orderType,
)
? HISTORICAL_ORDER_TYPE_BY_DETAILED_TYPE[order.orderType]
: 'market';

return {
orderId: order.oid?.toString() || '',
symbol: order.coin,
side: normalizedSide,
orderType: order.orderType?.toLowerCase().includes('limit')
? 'limit'
: 'market',
size: order.sz,
originalSize: order.origSz || order.sz,
price: order.limitPx || '0',
filledSize: filledSize.toString(),
remainingSize: currentSize.toString(),
...adaptedOrder,
orderType: historicalOrderType,
remainingSize: parseFloat(order.sz).toString(),
status: normalizedStatus,
timestamp: statusTimestamp,
lastUpdated: statusTimestamp,
detailedOrderType: order.orderType, // Full order type from exchange (e.g., 'Take Profit Limit', 'Stop Market')
isTrigger: order.isTrigger,
reduceOnly: order.reduceOnly,
Comment thread
michalconsensys marked this conversation as resolved.
};
});

Expand Down
2 changes: 2 additions & 0 deletions packages/perps-controller/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
} from './constants/perpsConfig.js';
import type { PerpsControllerState } from './PerpsController.js';
import type {
OrderDirection,
OrderType,
PerpsSelectedPaymentToken,
SortDirection,
Expand Down Expand Up @@ -149,6 +150,7 @@ type PendingTradeConfiguration = {
limitPrice?: string;
orderType?: OrderType;
reduceOnly?: boolean;
direction?: OrderDirection;
selectedPaymentToken?: PerpsSelectedPaymentToken | null;
};

Expand Down
4 changes: 2 additions & 2 deletions packages/perps-controller/src/utils/hyperLiquidAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ export function adaptOrderFromSDK(
);

// Extract basic fields with appropriate conversions
const orderId = rawOrder.oid.toString();
const orderId = rawOrder.oid?.toString() || '';
const symbol = rawOrder.coin;
const side: 'buy' | 'sell' = rawOrder.side === 'B' ? 'buy' : 'sell';
const detailedOrderType = rawOrder.orderType;
Expand All @@ -293,7 +293,7 @@ export function adaptOrderFromSDK(
// source for how the order actually executes.
orderType = getTriggerExecution(triggerOrderType);
} else if (
detailedOrderType.toLowerCase().includes('limit') ||
detailedOrderType?.toLowerCase().includes('limit') ||
rawOrder.limitPx
) {
orderType = 'limit';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,7 @@ describe('PerpsController', () => {
limitPrice: '45000',
orderType: 'limit' as const,
reduceOnly: true,
direction: 'short' as const,
};

controller.savePendingTradeConfiguration('BTC', config);
Expand All @@ -1127,6 +1128,20 @@ describe('PerpsController', () => {
expect(controller.getSelectedOrderType()).toBe('limit');
});

it('restores a short direction from a pending trade configuration', () => {
controller.savePendingTradeConfiguration('BTC', {
amount: '100',
direction: 'short',
});

const result = controller.getPendingTradeConfiguration('BTC');

expect(result).toEqual({
amount: '100',
direction: 'short',
});
});

it('returns undefined for non-existent pending configuration', () => {
const result = controller.getPendingTradeConfiguration('ETH');

Expand Down
Loading