[pull] main from MetaMask:main - #886
Merged
Merged
Conversation
## Explanation - Adds `@metamask/profile-controller`, a new package for managing MetaMask user profile state - `ProfileService` communicates with the MetaMask Profile API (`profile.api.cx.metamask.io`), exposing `getProfile`, `createProfile`, `replaceProfile`, `updateProfile`, `deleteProfile`, `checkUsernameAvailability`, `getXAuthUrl`, `connectX`, and `getXAccount` via the messenger, with superstruct validation on all inputs and responses - `ProfileController` manages profile state derived from the API and exposes all operations via the messenger; also exports `useGetProfile` and `useCheckUsernameAvailability` React hooks for UI components via `@metamask/react-data-query` ## References N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds authenticated profile CRUD, persisted user state, and X OAuth flows; mistakes could affect account identity data, though inputs/outputs are struct-validated and heavily tested. > > **Overview** > Introduces **`@metamask/profile-controller`**, a new monorepo package for MetaMask user profiles (username, bio, avatar, linked addresses, trading privacy) and optional **X (Twitter) linking**. > > **`ProfileService`** (extends `BaseDataService`) calls the MetaMask Profile API with bearer auth from `AuthenticationController:getBearerToken`, validates requests/responses with superstruct, and exposes CRUD, username availability, and X OAuth (`getXAuthUrl`, `connectX`, `getXAccount`) via messenger actions. > > **`ProfileController`** keeps persisted `profile` / optional `xProfile` state, maps API shapes to UI-friendly types, and delegates mutations to `ProfileService` (including guards when no profile exists and clearing state on delete). Broad Jest coverage is included for both layers. > > Repo wiring adds CODEOWNERS (`@MetaMask/accounts-engineers`), README/package graph entries, root/tsconfig references, `teams.json`, and oxlint suppression for the new jest config. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit dc46832. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…erLiquid agent (#10559) ## Explanation `PerpsController` signs through `KeyringController` messenger actions, so a client without a keyring (a web trading terminal on an EIP-1193 wallet, later Electron) can't use it. It also asks the main wallet to sign every HyperLiquid order, which an external wallet can't do: HyperLiquid L1 actions use chainId 1337, and wallets reject a typed-data chainId that doesn't match the connected chain. This PR adds two optional hooks and keeps the keyring path unchanged when neither is set: - **`PerpsPlatformDependencies.accountSigner`**: `signTypedData`, `signPersonalMessage`, optional `isReady` and `requiresSignatureConfirmation`. When set, HyperLiquid typed data and Lighter `personal_sign` go through it and no `KeyringController:*` action is called. The signing address still comes from the selected account. - **HyperLiquid agent signing**: - `providerCredentials.hyperliquid.getAgentSigner(account)` resolves an approved agent key for a main account and network, and L1 actions (orders, cancels, leverage) are signed by it. - User-signed actions (builder fee, withdraw) always stay on the main account. - `PerpsController:setAgentSigner` binds or pins an agent explicitly, and `PerpsController:clearAgentSigners` forgets them, for example on lock. - An agent the venue rejects (revoked or expired) is dropped and the write fails with `KEYRING_LOCKED`; optional `onAgentRejected(account, agentAddress)` tells the client. - Approving the agent stays the client's job. - **`PerpsController:prepareTradingWallet`** runs the deferred setup (HL migration, builder fee, referral; Lighter key registration) before the first order, so an external or hardware wallet signs it in one session, and reports readiness. **Breaking:** removes `LighterPersonalSigner` and `LighterAuthConfig.personalSigner` / `l1Address`. The controller never forwarded them, so they had no effect; `accountSigner.signPersonalMessage` replaces them. Agent handling: - the agent is bound to the main account and network it was approved for; - agent resolution is race-safe across account and network switches; - clearing agents doesn't tear down WebSockets; - the action types are generated rather than hand-edited. ## References - Mobile PR carrying this change as a Yarn patch: MetaMask/metamask-mobile#36966 ## Validation - Unit and integration tests for the account-signer and agent paths: 4,012 passing on Node 24 and 4,008 on Node 22 at the head `3e028d469f`, coverage thresholds met, including a test of the new public exports through `src/index.ts`. The agent-signing integration test runs the real controller, provider and client service against a faked SDK. Every review fix was checked red/green against the reverted source - Regression check: `main`'s own test suite (3,803 tests) run against this branch's source passes, except the 2 tests of the removed Lighter headless signer. The only test-side changes needed were the renamed methods of the internal `HyperLiquidWalletService` in mocks (`isKeyringUnlocked` → `isMainAccountSignerReady`, `isSelectedHardwareWallet` → `requiresSignatureConfirmation`, same answers for keyring hosts) and the SDK error class in one SDK mock - HyperLiquid testnet through the core harness on `110be95a9c` (the later commits only add tests and shorten doc comments) (market lifecycle with close, limit lifecycle with cancel, order edit, TP/SL, close position): - `KeyringController` messenger (the Mobile and Extension wiring): 5/5 pass, 8 to 11 `KeyringController` calls per recipe - `accountSigner`: 5/5 pass, 0 `KeyringController` calls, every signature through `accountSigner` - agent: 5/5 pass, 0 `KeyringController` and 0 main-account signatures; every order, cancel, edit, TP/SL and close signed by the agent key - `accountSigner` mode on `origin/main` (`d063750206`): every run stops at the harness's own guard, because that checkout does not export `PerpsAccountSigner`, before any order. It shows the mode is unavailable on `main`, not a difference in `main`'s trading - Agent lifecycle on HyperLiquid testnet on `110be95a9c`, with a fresh agent key: - the main account approved it through `accountSigner` as a named agent valid for one hour, and `extraAgents` listed it with the requested `validUntil` - `prepareTradingWallet` returned `ready: true` without any signature - `clearAgentSigners` left the one WebSocket open (none closed) while price updates continued, and the next L1 action asked `getAgentSigner` again - after the agent was revoked (zero address, same name), the next order failed with `KEYRING_LOCKED`, `onAgentRejected(account, agentAddress)` was called, and the agent was dropped and asked for again - `approveAgent` signature chain: HyperLiquid testnet accepted `approveAgent` signed with signature chain `0x1`, `0xa4b1` (Arbitrum One), `0x66eee` (Arbitrum Sepolia) and `0xaa36a7` (Sepolia), each with a matching EIP-712 domain chainId. Whether a wallet signs a domain chainId other than its active network was not tested - Mobile with this change as a Yarn patch of `459592184c` (keyring path, MetaMask/metamask-mobile#36966): type-check, all Perps unit (9,513) and integration (50) tests, HyperLiquid testnet journeys (smoke, position and order convergence, Pro TP/SL, order edit) and Lighter venue-key registration pass. The Lighter Lite trade journey could not run: its recipe checks an A/B-test flag that is no longer served Client note: Mobile's Perps integration harness mocks the internal `HyperLiquidWalletService`, so the version bump needs the two renamed methods in that mock (done in MetaMask/metamask-mobile#36966). ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Changes core trading signing paths (HyperLiquid L1 vs user-signed, Lighter registration), adds agent lifecycle and breaking Lighter config removal, with broad touch on order/cancel/TP-SL error handling. > > **Overview** > Adds optional **`PerpsPlatformDependencies.accountSigner`** so hosts without `KeyringController` can sign HyperLiquid EIP-712 and Lighter `personal_sign` from the selected account, with **`KEYRING_LOCKED`** when the signer is not ready and optional deferral of init-time prompts via **`requiresSignatureConfirmation`**. > > Adds **HyperLiquid agent signing** for L1 actions only: **`getAgentSigner`**, controller **`setAgentSigner` / `clearAgentSigners`**, venue rejection handling (**`onAgentRejected`**, agent eviction, **`KEYRING_LOCKED`** instead of “unknown wallet”), plus **`HyperLiquidWalletService`** routing (agent for `Exchange`/`Agent` typed data, main account for user-signed flows). New **`prepareTradingWallet`** (controller, aggregated provider, HyperLiquid, Lighter) runs migration, builder fee, referral, and Lighter key registration ahead of the first order and returns **`ReadyToTradeResult`**. > > **Breaking:** removes **`LighterPersonalSigner`** and **`LighterAuthConfig.personalSigner` / `l1Address`** in favor of **`accountSigner`**. HyperLiquid writes that fail because the signer is unavailable are classified as **`KEYRING_LOCKED`**, often without provider/`TradingService` error logging; batch **`cancelOrders`** reports per-order outcomes when some entries succeed. Docs/changelog and new exports (**`PerpsAccountSigner`**, **`PerpsAgentSigner`**, L1 EIP-712 constants) accompany the API surface. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3e028d4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Monte Lai <monte.lai@consensys.net>
…orders (#10588) ## Explanation `TradingService.flipPosition` sends leverage with no `marginMode`. `HyperLiquidProvider.#prepareAssetForTrading` then fell back to isolated and called `updateLeverage({ isCross: false })`. On a Cross position, HyperLiquid rejects that with "Cannot switch leverage type with open position". On Mobile, every one of these failures (64 events from 20 users over 30 days) was a position flip. When an order omits `marginMode`, HyperLiquid now uses the open position's mode. It reads positions from the fresh WebSocket cache and falls back to one REST read. With no position, or if the read fails, it keeps the old isolated default. Orders that set `marginMode` explicitly are unchanged. The fix is in the HyperLiquid provider, not in `flipPosition`: Lighter rejects any order that sets `marginMode`, so passing `position.leverage.type` from the shared service would break Lighter flips. ## References - Fixes [TAT-3994](https://consensyssoftware.atlassian.net/browse/TAT-3994) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them [TAT-3994]: https://consensyssoftware.atlassian.net/browse/TAT-3994?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
…t reconnect (#10589) ## Explanation Mobile reconnects Perps with `PerpsController.disconnect()`, a short cleanup delay, then `init()`. That covers a foreground resume after a failed ping, and account or network changes. `#getActiveProviderWhenReady` waited for the disconnect but not for the `init()` that follows it. An order submitted in that window woke up to an uninitialized controller and failed with `CLIENT_NOT_INITIALIZED`. This PR changes only the controller: - If an action waited on a `disconnect()` and the controller is still uninitialized afterwards, it waits up to `PERPS_CONSTANTS.ConnectionTimeoutMs` for the client's `init()` to start, then runs. If no `init()` follows, it fails as before. The controller never starts a connection itself. - If the reconnect switched the selected account, the network or the active provider, the action fails with `PROVIDER_LIFECYCLE_STALE` instead of running under the new context. The context is recorded when the action is issued. Behavior change: after a final disconnect with no follow-up `init()`, an action that was already in flight now takes up to 10 s to fail instead of failing right away. Actions issued after the disconnect are unaffected. The provider-side part of TAT-4041 (order-path reads over HTTP during a WebSocket reconnect, the `getMaxLeverage` 3x cap, 429 retry) is split into the stacked draft #10590. It stays in draft until a transport stress test settles the long-term transport choice. Not in this PR: - `PROVIDER_LIFECYCLE_STALE` for an order already in flight inside the provider when the account changes. Continuing under the new account would be wrong. How that code is shown to the user is up to the client. ## Reproduction The real headless `PerpsController`, SDK transports and signer ran against HyperLiquid testnet, with a client driver calling the real `disconnect()` and a delayed `init()`. | Controlled condition | Unfixed source (`1f15b7c`) | With the controller change | | --- | --- | --- | | Client `disconnect()` followed by delayed `init()` | Disconnect finished at 2,970 ms, and the order failed with `CLIENT_NOT_INITIALIZED` at 2,970 ms. `init()` only started at 3,470 ms. | `init()` finished at 5,260 ms. Order `61421723850` succeeded at 7,882 ms, was observed open, and was canceled by ID. 0 BTC orders and 0 positions after teardown. | That run was recorded on `9eb852d`, which also contained the provider changes now in #10590. The controller code on this path is the same apart from narrowing: the wait now only follows a `disconnect()`, not a reinitialization. I have not rerun the testnet reproduction on `9149042`. No Mobile or Extension UI was exercised. ## Validation (`9149042`) - `PerpsController` suites: `8 passed`, `465 passed`. - The 4 new lifecycle tests all fail on `main`: order placed across disconnect then init, bounded wait, and refusal after an account switch or a network switch. With only the context guard removed, just the 2 switch tests fail. - `oxlint` and `oxfmt` clean; `changelog:validate` passes; the package build type-checks. ## References - Fixes [TAT-4041](https://consensyssoftware.atlassian.net/browse/TAT-4041) (controller part). Provider part: #10590 - Related: [TAT-4013](https://consensyssoftware.atlassian.net/browse/TAT-4013) (`PROVIDER_LIFECYCLE_STALE` on account switch), [TAT-3871](https://consensyssoftware.atlassian.net/browse/TAT-3871) (retry and idempotency of the exchange call) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them [TAT-4041]: https://consensyssoftware.atlassian.net/browse/TAT-4041?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )