⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

feat(perps): perps controller migration from mobile#7749

Open
abretonc7s wants to merge 8 commits intomainfrom
feat/perps/controller-mocks
Open

feat(perps): perps controller migration from mobile#7749
abretonc7s wants to merge 8 commits intomainfrom
feat/perps/controller-mocks

Conversation

@abretonc7s
Copy link
Contributor

@abretonc7s abretonc7s commented Jan 28, 2026

Explanation

This PR migrates the complete PerpsController implementation from MetaMask Mobile to the Core monorepo as @metamask/perps-controller.

Background

The Perps (Perpetual Futures) feature was initially developed in MetaMask Mobile. To enable sharing this functionality across platforms (Mobile, Extension) and maintain a single source of truth, we are migrating the controller logic to the Core monorepo.

What's Included

Core Controller (~3,000 lines)

  • PerpsController.ts - Main controller extending BaseController with full trading functionality
  • selectors.ts - 8 state selectors for UI integration

Types & Constants

  • types/ - Comprehensive TypeScript type definitions for all Perps operations
  • constants/ - Configuration (perpsConfig, hyperLiquidConfig, errorCodes, eventNames)

Services (8 modules)

  • TradingService - Order placement, cancellation, position management
  • MarketDataService - Market info, prices, order book
  • AccountService - Account state management
  • DepositService - Deposit flow handling
  • EligibilityService - User eligibility checks
  • FeatureFlagConfigurationService - Remote feature flag integration
  • RewardsIntegrationService - MetaMask rewards integration
  • DataLakeService - Analytics data collection

Providers

  • HyperLiquidProvider - Full HyperLiquid protocol integration (~7,000 lines)
  • AggregatedPerpsProvider - Multi-provider aggregation layer
  • ProviderRouter - Routing logic for multi-provider support

Platform Services

  • HyperLiquidClientService - HTTP client for HyperLiquid API
  • HyperLiquidSubscriptionService - WebSocket subscription management
  • HyperLiquidWalletService - Wallet signing operations

Utilities (18 modules)

  • Calculation utilities (margin, PnL, position, order)
  • Formatting utilities (prices, amounts, dates)
  • Validation utilities (orders, TP/SL, HyperLiquid-specific)
  • Adapter utilities (HyperLiquid data transformation)

Architecture

The package uses dependency injection via PerpsPlatformDependencies interface to remain platform-agnostic:

type PerpsPlatformDependencies = {
  logger: PerpsLogger;           // Error logging (Sentry on Mobile)
  debugLogger: PerpsDebugLogger; // Dev logging
  metrics: PerpsMetrics;         // Analytics
  tracer: PerpsTracer;           // Performance tracing
  performance: PerpsPerformance; // Timing
  streamManager: PerpsStreamManager; // WebSocket control
  controllers: PerpsControllerAccess; // External controllers
};

This allows Mobile and Extension to provide their own implementations of these dependencies while sharing the core business logic.

ESLint Compliance

Most files pass Core ESLint rules. The following files have ESLint rules temporarily disabled due to the volume of code migrated:

  • PerpsController.ts
  • HyperLiquidProvider.ts
  • HyperLiquidSubscriptionService.ts

These suppressions allow the migration to proceed while maintaining functionality. They can be addressed incrementally in follow-up PRs.

Test Coverage

  • 40 unit tests covering controller initialization and all 8 selectors
  • Test mocks provided for:
    • @nktkas/hyperliquid SDK (ESM module)
    • Platform dependencies (serviceMocks.ts)
    • Provider interfaces (providerMocks.ts)

Coverage thresholds are currently set to 0% to allow incremental test migration. Full test coverage will be added in follow-up PRs.

References

  • Related to MetaMask Mobile Perps feature
  • This is the Core monorepo portion of the Perps migration effort

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note: This is a new package, so there are no breaking changes for existing consumers.


Note

High Risk
High risk due to introducing a large new controller that orchestrates trading, signing, and transaction submission via injected platform controllers, plus new external dependencies and reduced coverage enforcement during migration.

Overview
Adds the migrated, full-featured PerpsController to @metamask/perps-controller, including a rich persisted state model, messenger actions/events, dependency-injected platform infrastructure, provider initialization/reinit logic (testnet toggle, aggregated routing), and delegation to new services for trading, market data, deposits/withdrawals, eligibility, and analytics.

Updates package wiring for consumption and testing: re-exports selectors/types/constants from src/index.ts, introduces HyperLiquid SDK/Jest ESM mocking (moduleNameMapper + src/__mocks__/hyperliquidMock.ts and shared test mocks), adds new runtime/peer dependencies, and temporarily drops Jest coverage thresholds to 0 during the migration (with new migration guide + changelog entry).

Written by Cursor Bugbot for commit b40b1fc. This will update automatically on new commits. Configure here.

abretonc7s and others added 3 commits January 28, 2026 14:52
Complete migration of PerpsController implementation from MetaMask Mobile
to enable cross-platform sharing of Perps (Perpetual Futures) functionality.

This includes:
- PerpsController (~3,000 lines) with full trading functionality
- 8 state selectors for UI integration
- Comprehensive TypeScript type definitions
- 18 utility modules for calculations, formatting, validation
- 8 service modules (Trading, MarketData, Eligibility, etc.)
- HyperLiquidProvider with full protocol integration
- AggregatedPerpsProvider for multi-provider support
- Platform services for HyperLiquid client, subscriptions, wallet
- Test infrastructure with mocks and 40 unit tests

The package uses dependency injection via PerpsPlatformDependencies
interface to remain platform-agnostic, allowing Mobile and Extension
to provide their own implementations while sharing core business logic.
Add 17 test files migrated from metamask-mobile covering:
- Amount conversion, margin, PnL, and position calculations
- Order book grouping and order utilities
- Market data transformation and sorting
- HyperLiquid adapter and validation
- TP/SL validation and string parsing utilities

Add MIGRATION.md documenting migration status and coverage priorities.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@abretonc7s abretonc7s requested review from a team as code owners January 28, 2026 08:33
@socket-security
Copy link

socket-security bot commented Jan 28, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​nktkas/​hyperliquid@​0.30.39810010096100

View full report

@socket-security
Copy link

socket-security bot commented Jan 28, 2026

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

Ignoring alerts on:

  • @nktkas/hyperliquid@0.30.3
  • @noble/curves@2.0.1

View full report

@abretonc7s
Copy link
Contributor Author

@SocketSecurity ignore npm/@nktkas/hyperliquid@0.30.3

@abretonc7s
Copy link
Contributor Author

@SocketSecurity ignore npm/@noble/curves@2.0.1

@abretonc7s
Copy link
Contributor Author

@SocketSecurity ignore npm/@metamask/transaction-controller@62.10.0

@aganglada
Copy link
Contributor

aganglada commented Jan 29, 2026

I think we need to update the "@metamask/transaction-controller": "^62.11.0" in the package.json

abretonc7s and others added 2 commits January 30, 2026 09:32
- Update @metamask/transaction-controller to ^62.12.0
- Add PR references to CHANGELOG.md
- Regenerate yarn.lock

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@abretonc7s abretonc7s enabled auto-merge January 30, 2026 01:35
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Comment on lines +1449 to +1462
export type PerpsPlatformDependencies = {
// === Observability (stateless utilities) ===
logger: PerpsLogger;
debugLogger: PerpsDebugLogger;
metrics: PerpsMetrics;
performance: PerpsPerformance;
tracer: PerpsTracer;

// === Platform Services (mobile/extension specific) ===
streamManager: PerpsStreamManager;

// === Controller Access (ALL controllers consolidated) ===
controllers: PerpsControllerAccess;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the messenger API for as many of these as possible.

Comment on lines +22 to +25
branches: 0,
functions: 0,
lines: 0,
statements: 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initially disabled to get coverage and run the POC in extension.

'includeInDebugSnapshot',
);
// Debug snapshot should include controller state
expect(debugState).toBeDefined();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This defeats the purpose of the test. The idea is to ensure we have the expected properties included in the debug snapshot. Same applies below

Comment on lines +1 to +11
// Note: ESLint rules below are disabled because this file was migrated from Mobile
// which has different ESLint configurations. The following rules are Core-specific
// and would require extensive refactoring to satisfy. The code is functionally correct.
/* eslint-disable jsdoc/require-param-description, jsdoc/require-returns */
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable id-denylist, id-length */
/* eslint-disable consistent-return */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable promise/always-return */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these certainly seem worth it to address

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that all those are good in mobile but we maintain the source of truth in mobile, this allow to minimize the diff when we migrate to core fully (currently we want to publish an intiial version of the perps controller for extension)

// HIP-3 assets use format: hip3:dex_SYMBOL.svg (e.g., hip3:xyz_AAPL.svg)
// Regular assets use format: SYMBOL.svg (e.g., BTC.svg)
export const METAMASK_PERPS_ICONS_BASE_URL =
'https://raw.githubusercontent.com/MetaMask/contract-metadata/master/icons/eip155:999/';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing files in production using this API is not allowed by GitHub

Comment on lines +22 to +25
// Type guard - this function only supports 'transfer' type
// The type parameter is kept for API compatibility with mobile's version
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _type: 'transfer' = type;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow why this is required, given the type of type

const currentDepositId = generateDepositId();

// Get deposit routes from provider
const depositRoutes = provider.getDepositRoutes({ isTestnet: false });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this never supported in testnet mode?

}
}
}
} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert

Comment on lines +881 to +891
debug(): { timestamp: string; source: string; message: string } {
const debugInfo = {
timestamp: new Date().toISOString(),
source: '@metamask/perps-controller',
message: 'DEBUG v5 - Hello from Core PerpsController!',
};

this.debugLog('[PerpsController.debug]', debugInfo);

return debugInfo;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be included?

Copy link
Member

@Mrtenz Mrtenz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only got partially through reviewing this PR (due to its massive size), but here are some comments to look into. In addition:

  • Look into using the messenger for communication between controllers and services.
  • Look into reducing duplication. Extract types, utils, etc. into separate files, and reuse them between controllers and services.

Comment on lines +72 to +77
export function createMockOrderResult(): {
success: boolean;
orderId: string;
filledSize: string;
averagePrice: string;
} {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no type for this?

Comment on lines +349 to +352
const errorObj =
caughtError instanceof Error
? caughtError
: new Error(String(caughtError));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureError?

Comment on lines +34 to +35
/** Optional logger for error reporting (e.g., Sentry) */
logger?: PerpsLogger;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the messenger.

Comment on lines +231 to +233
caughtError instanceof Error
? caughtError
: new Error(String(caughtError));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureError?

Comment on lines +293 to +295
caughtError instanceof Error
? caughtError
: new Error(String(caughtError));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureError?

Comment on lines +620 to +627
const handleCandleEvent = (candleEvent: {
t: number;
o: string;
h: string;
l: string;
c: string;
v: string;
}): void => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no type for this?

Comment on lines +757 to +774
const intervalMap: Record<string, number> = {
'1m': 1 * 60 * 1000,
'3m': 3 * 60 * 1000,
'5m': 5 * 60 * 1000,
'15m': 15 * 60 * 1000,
'30m': 30 * 60 * 1000,
'1h': 60 * 60 * 1000,
'2h': 2 * 60 * 60 * 1000,
'4h': 4 * 60 * 60 * 1000,
'8h': 8 * 60 * 60 * 1000,
'12h': 12 * 60 * 60 * 1000,
'1d': 24 * 60 * 60 * 1000,
'3d': 3 * 24 * 60 * 60 * 1000,
'1w': 7 * 24 * 60 * 60 * 1000,
'1M': 30 * 24 * 60 * 60 * 1000, // Approximate
};

return intervalMap[interval] ?? 60 * 60 * 1000; // Default to 1 hour
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inMilliseconds.

* @param error - The caught error (could be Error, string, or unknown)
* @returns A proper Error instance
*/
function ensureError(error: unknown): Error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated from errorUtils.

* @param options.isTestnet - Whether to use testnet mode
*/
constructor(
deps: PerpsPlatformDependencies,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the messenger.

* @param options.isTestnet - Whether to use testnet mode
*/
constructor(
deps: PerpsPlatformDependencies,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the messenger.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this file is describing the PR itself, the audience being the author of this PR. Was this added by mistake?

@abretonc7s
Copy link
Contributor Author

Thanks @FrederikBolding, @Mrtenz, @Gudahtt - really appreciate the thorough feedback. I should have first written a comment providing context for this PR before asking for reviews. Let me provide that context now.

Context

This PR was a rough first version to get something usable for extension integration. The goal was to have working code that could unblock extension development while we continue active development in mobile. A few things to note:

  1. Tests weren't fully validated yet - The intention was to get usable code first, then properly migrate and validate tests. The debug snapshot test issue is a good example of this.

  2. ensureError pattern - This is a pattern we've been adopting recently, and migration is ongoing. Not all files have been updated yet in mobile.

  3. ESLint disables - Some need to remain to minimize diff with mobile. Can we accept this tradeoff for maintaining mobile sync? I'll review which are strictly necessary.

  4. Migration doc (PERPS_CONTROLLER_MIGRATION.md) - @Gudahtt This is already scoped within the perps-controller folder. I'll remove it once fully migrated and a 1.0.0 version is published.

Commitment to Messenger Pattern

Good point about using the messenger API for controller communication. Interestingly, I originally had the messenger pattern implemented, but moved to dependency injection thinking it would be more generic/portable. I'll re-implement the messenger pattern accordingly.

My plan: I'll refactor to use the messenger pattern in mobile first (since mobile is the source of truth), then bring those changes back to Core. This ensures we don't diverge and maintains a single source of truth.

Mobile as Source of Truth

There's very active development happening in mobile right now (MYX integration, Hyperliquid improvements, provider aggregation). The current approach keeps mobile as the source of truth to minimize merge conflicts and divergence. I'm using yalc as a temporary workaround to unblock extension, but proper Core integration is definitely preferred.

I recognize this isn't a perfect process, but I'm optimizing for outcome over process here. Keeping mobile as the source of truth allows us to move faster, which translates directly to additional revenue - a win for everyone. I'm committed to cleaning things up as we converge on the proper Core integration.

Guidance Request

Given the PR's size and the ongoing mobile development, I'd appreciate guidance on:

Which items are blocking vs can be addressed in follow-up PRs?

Here's how I'm thinking about prioritization:

P0 - Will address now (blocking)

  • Messenger pattern for controller communication
  • Remove migration doc (after 1.0.0 release)
  • Jest config: fix 0% coverage threshold

P1 - Follow-up PR

  • ensureError migration across all files
  • Code quality improvements (extract types/utils, reduce duplication)
  • Naming convention suggestions (PERPS_EVENT_PROPERTY, etc.)
  • inMilliseconds utility adoption

P2 - Can also address (via mobile-first workflow)

  • Full test coverage and snapshot fixes
  • Minor suggestions (Array.flat(), redundant comments, etc.)

All P1/P2 items will follow the same workflow: fix in mobile first, then sync back to Core.

Agreement Needed

Can we align on this mobile-first process? I'll track all feedback, address items in mobile, then bring changes back to Core. This keeps a single source of truth while still addressing all the valid concerns raised.

GitHub Raw API Note

@FrederikBolding - regarding the GitHub raw file API concern: this has been discussed and accepted within our team as an intentional tradeoff for the fallback mechanism.


Does this prioritization make sense? Are there items I've categorized as P1/P2 that you consider blocking? I want to make sure we're aligned on what's needed to move forward.

@gambinish gambinish mentioned this pull request Feb 4, 2026
4 tasks
@mcmire
Copy link
Contributor

mcmire commented Feb 6, 2026

@abretonc7s Sorry for the late reply here.

Usually when our team is asked to review a PR, we like to be thorough. We want to make sure things are being done in a sensible way and that they follow the standards we've set.

We're certainly happy that you reached out. However, this seems like an different situation, since the controller is already developed. So it doesn't seem that a review is what you're looking for. If I understand correctly, you want to make sure that engineers have all of the same functionality at the controller level they can use to develop Perps on extension? If this is true, then if speed is the motivation, you are certainly free to approve the PR and merge it (you are codeowners after all).

That said, I feel compelled to provide some advice based on your last comment:

  • I'm not convinced that treating mobile as the source of truth and continually syncing with core is the best idea. It seems like a workflow that's likely to cause bugs in Extension (plus you have to constantly deal with small differences such as lint configs). I'd really like to understand the obstacles you might be facing with core so we can make them better.
  • Deferring full test coverage until later also seems like it would allow for the presence of bugs as well. So it might be best to place that step higher in priority.
  • Finally, as much as AI is a useful tool, I've noticed it tends to generate a lot of code by default, and it's tempting to keep it all. This is more of a PSA, but it's good to use your discretion, continually check the output that AI produces, and try to keep things as simple as possible (this helps the humans be able to understand code, too).

Anyway, I know that @gambinish has also published a preview build for these changes in #7841, and there was some talk I overheard about perhaps using this in production. I also want to advise that preview builds are really for testing, and we should treat them as ephemeral. So from a best practices perspective, once we want to release Perps on Extension, I think it would be better to cut a "production" release of perps-controller in this repo and use that rather than a preview build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants