Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2ade60e
feat(perps): add slippage estimate display and persisted max slippage…
abretonc7s Jun 8, 2026
c9cdb9b
fix(perps): address self-review feedback for slippage config (TAT-1043)
abretonc7s Jun 8, 2026
9bcc630
fix(perps): address self-review feedback (TAT-1043)
abretonc7s Jun 8, 2026
0ede7e8
fix(perps): address external review for slippage config (TAT-1043)
abretonc7s Jun 9, 2026
cf01625
fix(perps): use camelCase remote flag key and rollout shape for slipp…
abretonc7s Jun 9, 2026
8261ab0
fix(perps): address self-review feedback (TAT-1043)
abretonc7s Jun 9, 2026
fcba0eb
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 9, 2026
033330b
fix: address review comments on PR #43357
abretonc7s Jun 9, 2026
4bf3d00
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 9, 2026
6d1ff88
fix: prevent slippage hook from deactivating shared order book
abretonc7s Jun 9, 2026
eaffaf0
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 9, 2026
f6a5ff2
fix: gate perps slippage UI on max-slippage loading state
abretonc7s Jun 9, 2026
7b62737
fix: address review comments on PR #43357
abretonc7s Jun 9, 2026
d67b6d9
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 9, 2026
7b84f82
fix: address review comments on PR #43357
abretonc7s Jun 9, 2026
e7f5f8c
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 9, 2026
33037de
fix(perps): clear stale slippage error after max save
abretonc7s Jun 9, 2026
3db46a4
fix(perps): request slippage book depth on order entry stream
abretonc7s Jun 9, 2026
2242007
fix: address CI feedback
abretonc7s Jun 9, 2026
6702b6e
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 9, 2026
766493b
fix: address review comments on PR #43357
abretonc7s Jun 9, 2026
35b7ff7
fix(perps): show max slippage while estimate loads
abretonc7s Jun 9, 2026
8b41663
fix(messenger): delegate SnapController state to MultichainAccountSer…
abretonc7s Jun 9, 2026
b844316
fix(perps): align slippage estimate direction with form state
abretonc7s Jun 9, 2026
66107e8
fix(test): restore perps-controller mocks for slippage constants
abretonc7s Jun 9, 2026
3299258
fix(perps): recap prefill on balance and gate submit on estimate
abretonc7s Jun 9, 2026
7772e1b
fix(messenger): align MultichainAccountService delegate types
abretonc7s Jun 9, 2026
7b9c17e
fix(messenger): align delegates with multichain-account-service 10.0.2
abretonc7s Jun 9, 2026
267b6d6
fix(perps): block slippage modal until preference loads
abretonc7s Jun 9, 2026
e56953f
fix(perps): recap default amount with current form leverage
abretonc7s Jun 9, 2026
50af04c
fix(perps): format usePerpsOrderForm for oxfmt CI gate
abretonc7s Jun 9, 2026
2e7bd1c
Merge branch 'main' into TAT-1043-feat-add-perps-slippage-config
abretonc7s Jun 10, 2026
93f1ff5
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 11, 2026
eef4891
fix: register perpsSlippageConfig2 feature flag
abretonc7s Jun 11, 2026
7f23ad1
fix(perps): disable perpsSlippageConfig2 in e2e perps fixture
abretonc7s Jun 11, 2026
2477325
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 11, 2026
7720371
fix: address review comments on PR #43357
abretonc7s Jun 11, 2026
9ca75f2
Merge remote-tracking branch 'origin/main' into TAT-1043-feat-add-per…
abretonc7s Jun 11, 2026
5981254
fix: address review comments on PR #43357
abretonc7s Jun 11, 2026
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
37 changes: 37 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions app/_locales/en_GB/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions app/scripts/controllers/perps/perps-stream-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,36 @@ describe('PerpsStreamBridge', () => {
expect(emit).toHaveBeenCalledWith('orderBook', { bids: [], asks: [] });
});

it('forwards levels / nSigFigs / mantissa to the controller', async () => {
const controller = createMockController();
const { bridge } = createBridge({
controller: controller as unknown as PerpsController,
});
const api = bridge.bridgeApi();

await (
api.perpsActivateOrderBookStream as (p: {
symbol: string;
levels?: number;
nSigFigs?: 2 | 3 | 4 | 5;
mantissa?: 2 | 5;
}) => Promise<void>
)({
symbol: 'ETH',
levels: 25,
nSigFigs: 4,
mantissa: 5,
});

expect(controller.subscribeToOrderBook).toHaveBeenCalledWith({
symbol: 'ETH',
levels: 25,
nSigFigs: 4,
mantissa: 5,
callback: expect.any(Function),
});
});

it('deactivateOrderBookStream tears down order book subscription', async () => {
const controller = createMockController();
const unsub = jest.fn();
Expand Down
24 changes: 20 additions & 4 deletions app/scripts/controllers/perps/perps-stream-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,20 @@ export class PerpsStreamBridge {
perpsDeactivatePriceStream: () => {
this.#tearDownChannel('prices');
},
perpsActivateOrderBookStream: async ({ symbol }: { symbol: string }) => {
perpsActivateOrderBookStream: async ({
symbol,
levels,
nSigFigs,
mantissa,
}: {
symbol: string;
levels?: number;
nSigFigs?: 2 | 3 | 4 | 5;
mantissa?: 2 | 5;
}) => {
await this.#initAndActivate();
if (this.#isConnectionAlive()) {
this.#activateOrderBookStream(symbol);
this.#activateOrderBookStream({ symbol, levels, nSigFigs, mantissa });
}
},
perpsDeactivateOrderBookStream: () => {
Expand Down Expand Up @@ -586,12 +596,18 @@ export class PerpsStreamBridge {
);
}

#activateOrderBookStream(symbol: string): void {
#activateOrderBookStream(params: {
symbol: string;
levels?: number;
nSigFigs?: 2 | 3 | 4 | 5;
mantissa?: 2 | 5;
}): void {
const { symbol } = params;
this.#tearDownChannel('orderBook');
if (symbol) {
this.#addDynamicSubscription('orderBook', () =>
this.#controller.subscribeToOrderBook({
symbol,
...params,
callback: (data: unknown) => this.#emit('orderBook', data),
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ jest.mock('@metamask/perps-controller', () => ({
clearPendingTransactionRequests: jest.fn(),
saveOrderBookGrouping: jest.fn(),
getOrderBookGrouping: jest.fn(),
getMaxSlippage: jest.fn(),
setMaxSlippage: jest.fn(),
getActiveProvider: jest.fn().mockReturnValue({
getUserHistory: jest.fn(),
getUserNonFundingLedgerUpdates: jest.fn(),
Expand Down Expand Up @@ -581,6 +583,8 @@ describe('PerpsControllerInit', () => {
],
['perpsSaveOrderBookGrouping', 'saveOrderBookGrouping'],
['perpsGetOrderBookGrouping', 'getOrderBookGrouping'],
['perpsGetMaxSlippage', 'getMaxSlippage'],
['perpsSetMaxSlippage', 'setMaxSlippage'],
['perpsClearDepositResult', 'clearDepositResult'],
['perpsClearWithdrawResult', 'clearWithdrawResult'],
['perpsGetBlockExplorerUrl', 'getBlockExplorerUrl'],
Expand Down
4 changes: 4 additions & 0 deletions app/scripts/messenger-client-init/perps-controller-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ type PerpsActionName =
| 'perpsClearPendingTransactionRequests'
| 'perpsSaveOrderBookGrouping'
| 'perpsGetOrderBookGrouping'
| 'perpsGetMaxSlippage'
| 'perpsSetMaxSlippage'
| 'perpsGetUserHistory'
| 'perpsClearDepositResult'
| 'perpsClearWithdrawResult'
Expand Down Expand Up @@ -468,6 +470,8 @@ function getApi(
messengerClient.saveOrderBookGrouping.bind(messengerClient),
perpsGetOrderBookGrouping:
messengerClient.getOrderBookGrouping.bind(messengerClient),
perpsGetMaxSlippage: messengerClient.getMaxSlippage.bind(messengerClient),
perpsSetMaxSlippage: messengerClient.setMaxSlippage.bind(messengerClient),

// -- Provider passthrough (read-guard) --
perpsGetUserHistory: read(
Expand Down
14 changes: 14 additions & 0 deletions shared/constants/perps-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export const PERPS_EVENT_PROPERTY = {
SCREEN_NAME: 'screen_name',
ACTION_TYPE: 'action_type',
ORDER_TIMESTAMP: 'order_timestamp',
MAX_SLIPPAGE_PCT: 'max_slippage_pct',
MAX_SLIPPAGE_SOURCE: 'max_slippage_source',
ESTIMATED_SLIPPAGE_PCT: 'estimated_slippage_pct',
SETTING_TYPE: 'setting_type',
} as const;

export const PERPS_EVENT_VALUE = {
Expand Down Expand Up @@ -78,6 +82,9 @@ export const PERPS_EVENT_VALUE = {
CLOSE_ALL_TAPPED: 'close_all_tapped',
CLOSE_ALL_CONFIRMED: 'close_all_confirmed',
CLOSE_ALL_CANCELLED: 'close_all_cancelled',
SLIPPAGE_CONFIG_OPENED: 'slippage_config_opened',
SLIPPAGE_CONFIG_CHANGED: 'slippage_config_changed',
SLIPPAGE_LIMIT_BLOCKED_ORDER: 'slippage_limit_blocked_order',
},
BUTTON_CLICKED: {
DEPOSIT: 'deposit',
Expand Down Expand Up @@ -147,6 +154,13 @@ export const PERPS_EVENT_VALUE = {
FUNDING: 'funding',
DEPOSITS: 'deposits',
},
MAX_SLIPPAGE_SOURCE: {
DEFAULT: 'default',
USER_CONFIGURED: 'user_configured',
},
SETTING_TYPE: {
SLIPPAGE: 'slippage',
},
} as const;

export enum PerpsAnalyticsEvent {
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/feature-flags/feature-flag-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2261,6 +2261,17 @@ export const FEATURE_FLAG_REGISTRY: Record<string, FeatureFlagRegistryEntry> = {
status: FeatureFlagStatus.Active,
},

perpsSlippageConfig2: {
name: 'perpsSlippageConfig2',
type: FeatureFlagType.Remote,
inProd: true,
productionDefault: {
enabled: true,
minimumVersion: '13.30.0',
},
status: FeatureFlagStatus.Active,
},

vipProgramEnabled: {
name: 'vipProgramEnabled',
type: FeatureFlagType.Remote,
Expand Down
14 changes: 14 additions & 0 deletions test/e2e/tests/perps/perps-fixture-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ const PERPS_ELIGIBLE_REMOTE_FEATURE_FLAGS = {
confirmations_pay_post_quote: PERPS_WITHDRAW_CONFIRMATION_DISABLED_FLAG,
perpsEnabledVersion: { enabled: true, minimumVersion: '0.0.0' },
perpsPerpTradingGeoBlockedCountriesV2: { blockedRegions: [] },
// Disable the configurable max-slippage controls in the generic Perps E2E
// fixture. When enabled, market-order submit is gated on a live order-book
// slippage estimate (usePerpsEstimatedSlippage) that the lifecycle WS mock
// does not feed, leaving submit-order-button permanently disabled. The flag
// is on in production (registry value), so it stays registered; tests that
// need the slippage UI can opt in explicitly. Covered by unit tests + recipe.
perpsSlippageConfig2: { enabled: false, minimumVersion: '0.0.0' },
Comment thread
cursor[bot] marked this conversation as resolved.
};

/**
Expand Down Expand Up @@ -258,6 +265,13 @@ async function mockEligibleFeatureFlags(server: Mockttp): Promise<void> {
// eslint-disable-next-line @typescript-eslint/naming-convention
confirmations_pay: { name: 'empty' },
perpsPerpTradingGeoBlockedCountriesV2: { blockedRegions: [] },
// Mirror the seeded controller state: the background
// RemoteFeatureFlagController refetches /v1/flags on load and would
// otherwise overwrite the seeded `enabled: false` with the production
// default (`enabled: true`), re-enabling slippage gating and leaving
// market submit disabled without order-book estimates.
perpsSlippageConfig2:
PERPS_ELIGIBLE_REMOTE_FEATURE_FLAGS.perpsSlippageConfig2,
});
await server
.forGet('https://client-config.api.cx.metamask.io/v1/flags')
Expand Down
57 changes: 57 additions & 0 deletions test/mocks/metamask-perps-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,68 @@ const mockPerpsErrorCodes = new Proxy(
{ get: (_target, prop) => String(prop) },
);

const mockOrderSlippageConfig = {
DefaultMarketSlippageBps: 300,
DefaultTpslSlippageBps: 1000,
DefaultLimitSlippageBps: 100,
};

const mockMaxSlippageBounds = {
MinBps: 10,
MaxBps: 1000,
StepBps: 10,
};

const mockPerformanceConfig = {
SlippageEstimateThrottleMs: 250,
SlippageEstimateBookLevels: 10,
};

const mockTradingDefaults = {
leverage: 3,
marginPercent: 10,
takeProfitPercent: 0.3,
stopLossPercent: 0.1,
amount: {
mainnet: 10,
testnet: 10,
},
};

/**
* Simplified max-amount helper for unit tests (matches controller shape; omits
* position-size rounding details that are covered by controller tests).
*
* @param {object} params
* @param {number} params.spendableBalance
* @param {number} params.assetPrice
* @param {number} params.assetSzDecimals
* @param {number} params.leverage
* @returns {number}
*/
function mockGetMaxAllowedAmount({
spendableBalance,
assetPrice,
assetSzDecimals,
leverage,
}) {
if (spendableBalance === 0 || !assetPrice || assetSzDecimals === undefined) {
return 0;
}
return Math.max(0, Math.floor(spendableBalance * leverage * 0.99));
}

module.exports = {
PERPS_EVENT_PROPERTY: mockPerpsEventPropertyKeys,
PERPS_EVENT_VALUE: mockPerpsEventValueLiterals,
PerpsAnalyticsEvent: mockPerpsAnalyticsEventNames,
PERPS_ERROR_CODES: mockPerpsErrorCodes,
ORDER_SLIPPAGE_CONFIG: mockOrderSlippageConfig,
MAX_SLIPPAGE_BOUNDS: mockMaxSlippageBounds,
PERFORMANCE_CONFIG: mockPerformanceConfig,
TRADING_DEFAULTS: mockTradingDefaults,
getMaxAllowedAmount: mockGetMaxAllowedAmount,
BASIS_POINTS_DIVISOR: 10000,
MARKET_CATEGORIES: mockMarketCategories,
isHip3Market: mockIsHip3Market,
getMarketTypeFilter: mockGetMarketTypeFilter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jest.mock('../../../../../shared/lib/perps-formatters', () => ({
}));

jest.mock('@metamask/perps-controller', () => ({
...jest.requireActual('@metamask/perps-controller'),
PERPS_ERROR_CODES: {
CLIENT_NOT_INITIALIZED: 'CLIENT_NOT_INITIALIZED',
CLIENT_REINITIALIZING: 'CLIENT_REINITIALIZING',
Expand Down
26 changes: 26 additions & 0 deletions ui/components/app/perps/constants/slippageConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
PERPS_SLIPPAGE_DEFAULT_BPS,
PERPS_SLIPPAGE_MAX_BPS,
PERPS_SLIPPAGE_MIN_BPS,
PERPS_SLIPPAGE_STEP_BPS,
bpsToPercent,
percentToBps,
} from './slippageConfig';

describe('slippageConfig constants', () => {
it('uses controller default of 3%', () => {
expect(PERPS_SLIPPAGE_DEFAULT_BPS).toBe(300);
expect(bpsToPercent(PERPS_SLIPPAGE_DEFAULT_BPS)).toBe(3);
});

it('exposes 0.1% to 10% bounds in 0.1% steps', () => {
expect(bpsToPercent(PERPS_SLIPPAGE_MIN_BPS)).toBe(0.1);
expect(bpsToPercent(PERPS_SLIPPAGE_MAX_BPS)).toBe(10);
expect(bpsToPercent(PERPS_SLIPPAGE_STEP_BPS)).toBe(0.1);
});

it('converts percent and bps', () => {
expect(percentToBps(3)).toBe(300);
expect(bpsToPercent(300)).toBe(3);
});
});
Loading
Loading