Skip to content
Merged
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
3 changes: 3 additions & 0 deletions packages/perps-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `PerpsController.getMarginModeLock` (and the `PerpsController:getMarginModeLock` messenger action) plus the optional `PerpsProvider.getMarginModeLock`, reporting the margin mode an asset is locked to by an open position or resting order/TWAP so clients can keep their margin-mode picker in sync with what order placement will accept. HyperLiquid implements it; other providers report `not_implemented`. ([#10414](https://github.com/MetaMask/core/pull/10414))
- Implement `getMarginModeLock` for Lighter, reporting the mode an open position binds to its market. ([#10414](https://github.com/MetaMask/core/pull/10414))
- Add optional `supportedMarginModes` to ready order capabilities; HyperLiquid reports `['isolated', 'cross']` for main-DEX markets and `['isolated']` for HIP-3 or isolated-only assets, so clients stop inferring margin-mode support from the provider. ([#10414](https://github.com/MetaMask/core/pull/10414))
- Persist the Isolated/Cross margin-mode pick per market and network in `tradeConfigurations[network][symbol].marginMode`, so clients can restore it after the order form remounts and share it across Mobile and Extension ([#10464](https://github.com/MetaMask/core/pull/10464))
- Add `getMarginMode(symbol)` and `saveMarginMode(symbol, marginMode)` methods, exposed as the `PerpsController:getMarginMode` and `PerpsController:saveMarginMode` messenger actions (`PerpsControllerGetMarginModeAction`, `PerpsControllerSaveMarginModeAction`). `saveMarginMode` ignores values other than `isolated` or `cross`.
- Add the `selectMarginMode(state, symbol)` selector and an optional `marginMode` field on `TradeConfiguration`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ export type PerpsControllerGetOrderCapabilitiesAction = {
handler: PerpsController['getOrderCapabilities'];
};

/**
* Get the margin mode the market is currently locked to by an open
* position or resting order, through the active provider route used by
* order placement. Never throws; failures report an unavailable status.
*
* @param params - Market and optional provider route.
* @returns The provider-reported margin-mode lock.
*/
export type PerpsControllerGetMarginModeLockAction = {
type: `PerpsController:getMarginModeLock`;
handler: PerpsController['getMarginModeLock'];
};

/**
* Build a Scale price ladder using the active provider's venue rules.
*
Expand Down Expand Up @@ -1386,6 +1399,7 @@ export type PerpsControllerMethodActions =
| PerpsControllerGetActiveProviderAction
| PerpsControllerGetActiveProviderOrNullAction
| PerpsControllerGetOrderCapabilitiesAction
| PerpsControllerGetMarginModeLockAction
| PerpsControllerGetScalePriceLadderAction
| PerpsControllerPlaceOrderAction
| PerpsControllerEditOrderAction
Expand Down
73 changes: 73 additions & 0 deletions packages/perps-controller/src/PerpsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import type {
GetMarketDataWithPricesParams,
GetMarketsParams,
GetOrderCapabilitiesParams,
GetMarginModeLockParams,
GetScalePriceLadderParams,
GetOrderFillsParams,
GetOrdersParams,
Expand All @@ -110,13 +111,15 @@ import type {
MarketInfo,
Order,
OrderCapabilitiesUnavailableReason,
MarginModeLockUnavailableReason,
OrderDirection,
OrderFill,
OrderParams,
OrderResult,
PerpsControllerConfig,
PerpsMarketData,
PerpsOrderCapabilities,
PerpsMarginModeLock,
PerpsScalePriceLadder,
ScalePriceLadderUnavailableReason,
PerpsPendingManualRecovery,
Expand Down Expand Up @@ -934,6 +937,7 @@ const MESSENGER_EXPOSED_METHODS = [
'getOrderBookGrouping',
'getOrderBookPreferences',
'getOrderCapabilities',
'getMarginModeLock',
'getScalePriceLadder',
'getOrderFills',
'getOrders',
Expand Down Expand Up @@ -2803,6 +2807,75 @@ export class PerpsController extends BaseController<
}
}

/**
* Get the margin mode the market is currently locked to by an open
* position or resting order, through the active provider route used by
* order placement. Never throws; failures report an unavailable status.
*
* @param params - Market and optional provider route.
* @returns The provider-reported margin-mode lock.
*/
async getMarginModeLock(
params: GetMarginModeLockParams,
): Promise<PerpsMarginModeLock> {
let activeProvider: PerpsProvider;
try {
activeProvider = await this.#getActiveProviderWhenReady();
} catch (error) {
this.#debugLog('PerpsController: Margin mode lock unavailable', {
error: ensureError(error, 'PerpsController.getMarginModeLock').message,
});
return this.#getUnavailableMarginModeLock(
'provider_unavailable',
params.providerId,
);
}

const resolvedProviderId =
params.providerId ?? this.#getDirectProviderId(activeProvider);
if (this.#hasConflictingProviderRoute(params.providerId, activeProvider)) {
return this.#getUnavailableMarginModeLock(
'provider_not_routable',
resolvedProviderId,
);
}
if (!activeProvider.getMarginModeLock) {
return this.#getUnavailableMarginModeLock(
'not_implemented',
resolvedProviderId,
);
}

try {
const lock = await activeProvider.getMarginModeLock(params);
if (
lock.status === 'unavailable' &&
lock.providerId === undefined &&
resolvedProviderId !== undefined
) {
return { ...lock, providerId: resolvedProviderId };
}
return lock;
} catch (error) {
this.#debugLog('PerpsController: Margin mode lock unavailable', {
error: ensureError(error, 'PerpsController.getMarginModeLock').message,
});
return this.#getUnavailableMarginModeLock(
'provider_unavailable',
resolvedProviderId,
);
}
}

#getUnavailableMarginModeLock(
reason: MarginModeLockUnavailableReason,
providerId: PerpsProviderType | undefined,
): PerpsMarginModeLock {
return providerId
? { status: 'unavailable', providerId, reason }
: { status: 'unavailable', reason };
}

/**
* Build a Scale price ladder using the active provider's venue rules.
*
Expand Down
5 changes: 5 additions & 0 deletions packages/perps-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export type {
PerpsControllerGetOrderBookGroupingAction,
PerpsControllerGetOrderBookPreferencesAction,
PerpsControllerGetOrderCapabilitiesAction,
PerpsControllerGetMarginModeLockAction,
PerpsControllerGetScalePriceLadderAction,
PerpsControllerGetOrderFillsAction,
PerpsControllerGetOrdersAction,
Expand Down Expand Up @@ -295,6 +296,10 @@ export type {
FeeCalculationParams,
FeeCalculationResult,
GetOrderCapabilitiesParams,
GetMarginModeLockParams,
MarginModeLockReason,
MarginModeLockUnavailableReason,
PerpsMarginModeLock,
GetScalePriceLadderParams,
OrderCapabilitiesUnavailableReason,
DirectProviderOrderCapabilitiesUnavailableReason,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import type {
GetHistoricalPortfolioParams,
GetMarketsParams,
GetOrderCapabilitiesParams,
GetMarginModeLockParams,
GetScalePriceLadderParams,
GetOrderFillsParams,
GetOrdersParams,
Expand All @@ -68,6 +69,7 @@ import type {
OrderResult,
PerpsMarketData,
PerpsOrderCapabilities,
PerpsMarginModeLock,
PerpsScalePriceLadder,
PerpsPendingManualRecovery,
PerpsRecoveredDispatch,
Expand Down Expand Up @@ -342,6 +344,54 @@ export class AggregatedPerpsProvider implements PerpsProvider {
}
}

/**
* Resolve the margin-mode lock with the same explicit-provider/default-
* provider selection used by order placement.
*
* @param params - Market and optional provider route.
* @returns The lock reported by the selected provider.
*/
async getMarginModeLock(
params: GetMarginModeLockParams,
): Promise<PerpsMarginModeLock> {
const providerId = params.providerId ?? this.#defaultProvider;
const provider = this.#providers.get(providerId);
if (!provider) {
return {
status: 'unavailable',
providerId,
reason: 'provider_not_found',
};
}
if (!provider.getMarginModeLock) {
return { status: 'unavailable', providerId, reason: 'not_implemented' };
}
try {
const lock = await provider.getMarginModeLock({ ...params, providerId });
if (lock.providerId !== undefined && lock.providerId !== providerId) {
return {
status: 'unavailable',
providerId,
reason: 'provider_not_routable',
};
}
return { ...lock, providerId };
} catch (error) {
this.#deps.debugLogger.log(
'[AggregatedPerpsProvider] Margin mode lock unavailable',
{
providerId,
error: error instanceof Error ? error.message : String(error),
},
);
return {
status: 'unavailable',
providerId,
reason: 'provider_unavailable',
};
}
}

/**
* Normalize a Scale price ladder through the selected provider route.
*
Expand Down
Loading
Loading