fix(perps): default spot-funded order entry to perps balance cp-7.72.2#29150
fix(perps): default spot-funded order entry to perps balance cp-7.72.2#29150geositta wants to merge 17 commits into
Conversation
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
8f716de to
24a94e3
Compare
The generated action types file was stale after adding the selectedPaymentTokenSource parameter, causing yarn lint to fail.
…cycle validation (#29171) ## **Description** Small cleanup on top of Matt's TAT-3016 hotfix, plus an agentic validation harness that proves the fix contract holds end-to-end. Matt's hotfix surfaces `availableToTradeBalance = withdrawable + spot USDC` for order-entry while keeping `availableBalance = withdrawable` for the withdraw path. [#29150](#29150) introduced that change but carried a lot of incidental churn — a state machine for payment-token sources, a wrapper helper that only added noise, and a 215-line refactor of `useDefaultPayWithTokenWhenNoPerpsBalance`. None of those were part of the fix contract. This PR strips them and inlines a simpler inference: clear the saved payment token only when the user now has native buying power **and** the saved token matches the auto-fallback candidate. Net production-code delta vs the base hotfix: **−182 lines**. ### Validation harness One recipe drives the shared Trading account (currently in the exact "Unified Mode + spot-only" state that broke orders) through three phases: capture initial state, flip HL abstraction to Standard via `userSetAbstraction`, capture, flip back to Unified, capture. Setup closes open positions (HL rejects abstraction flips otherwise); teardown restores Unified. Three small additions make this composable for future perps PRs: - A `testID` on `PerpsWithdrawView`'s available-balance text so automation can read what users see. - `HyperLiquidProvider.getExchangeClient()` — narrow escape hatch that lets admin/test flows drive any SDK action without growing a controller method per operation. Not on the `PerpsProvider` interface. - Two reusable flows: `hl-balance-validation` (captures AccountState + PerpsMarketListView balance + PerpsWithdrawView balance, asserts the fold invariant and no-fold-leak on withdraw) and `hl-provision-fixture` (wraps `userSetAbstraction` + `usdClassTransfer`). Every timing gate in the recipe is a `wait_for` polling a deterministic condition. No arbitrary `wait: Nms`. ### Out of scope (deliberate) - No contract reshape (`availableBalance → spendableBalance` / `withdrawableBalance`) — that's [TAT-3047](https://consensyssoftware.atlassian.net/browse/TAT-3047). - No withdraw-path logic edits. `availableBalance` semantics preserved. - No MYX logic beyond the trivial `availableToTradeBalance = availableBalance` field. - No upstream `@metamask/perps-controller` changes. - Mode-aware fold gating (honour Unified vs Standard on the mobile side) — also [TAT-3047](https://consensyssoftware.atlassian.net/browse/TAT-3047). ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: [TAT-3016](https://consensyssoftware.atlassian.net/browse/TAT-3016) Context: - Supersedes: [#29150](#29150) (credit to @geositta) - Prior merged fix (complementary): [#29110](#29110) - Follow-up: [TAT-3047](https://consensyssoftware.atlassian.net/browse/TAT-3047) ## **Manual testing steps** The useful path is automated. A reviewer who wants to reproduce can run the recipe against the shared Trading account: ```gherkin Feature: TAT-3016 HL abstraction cycle Scenario: Spot-funded Trading account cycled through Unified / Standard / Unified Given the Trading account (0x316B…01fA) is in Unified Mode with spot USDC collateral And all open positions have been closed When the recipe captures AccountState + PerpsMarketListView balance + PerpsWithdrawView balance And flips HL abstraction to Standard via userSetAbstraction And re-captures And flips back to Unified And captures once more Then availableToTradeBalance >= availableBalance in every phase And PerpsMarketListView shows the fold (≈ $100.76) in every phase And PerpsWithdrawView shows "Available Perps balance: $0" in every phase And teardown leaves the account in Unified Mode ``` Observed values on the shared Trading account: | Phase | `availableBalance` | `availableToTradeBalance` | Market-list balance | Withdraw balance | |---|---|---|---|---| | Initial (Unified) | $0.00 | $100.75 | **$100.76** | **Available Perps balance: $0** | | After flip → Standard | $0.00 | $100.75 | **$100.76** | **Available Perps balance: $0** | | Restored (Unified) | $0.00 | $100.75 | **$100.76** | **Available Perps balance: $0** | Identical balances across Unified / Standard flips are **intentional** per #29150's product assumption: "spot USDC is treated as interchangeable with Perps trading collateral for the markets we currently surface." HL's HIP-3 / non-USDC markets are deferred to TAT-3047. ## **Screenshots/Recordings** ### **Before** This PR layers on top of Matt's already-landing hotfix; the user-visible behavior does not change. The harness below demonstrates the hotfix contract is preserved across the cleanup. ### **After** Three-phase cycle evidence. Two screenshots per phase — market-list (shows fold) and withdraw (shows withdrawable-only). | | Market list | Withdraw | |---|---|---| | **Initial (Unified)** |  |  | | **After flip → Standard** |  |  | | **Restored (Unified)** |  |  | ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - [ ] I've tested with a power user scenario - [ ] I've instrumented key operations with Sentry traces for production performance metrics ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. [TAT-3047]: https://consensyssoftware.atlassian.net/browse/TAT-3047?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
…r with PerpsMarketDetailsView
…al test for addSpotBalanceToAccountState
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7a22ab7. Configure here.
…d helpers Consolidate computePreferredFallbackPayTokenCandidate, arePaymentTokensEqual, useHasNativeTradeablePerpsBalance, and usePreferredFallbackPayTokenCandidate directly into useDefaultPayWithTokenWhenNoPerpsBalance. Simplify useInitPerpsPaymentToken by removing the stale auto-fallback clearing path and guarding re-application with the appliedPendingTokenRef.
AI PR Analysis🚫 Merge safe: false | 🟠 Risk: high
AI analysis did not complete. Manual review recommended. |
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
The changes are well-scoped to the Perps feature domain. No changes to core Engine, navigation, browser, accounts, networks, or other unrelated features. The new Performance Test Selection: |
…29203) ## **Description** Adds agentic workflow json directly to main, separately from cherry pick branch: MetaMask#29150 ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Adds MetaMask#29510 agentic workflows separately from cherry pick branch ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: only adds internal agentic JSON workflows and a docs link, with no runtime app code changes. Main risk is operational (admin flow invokes mainnet HyperLiquid transfers if executed). > > **Overview** > Adds two new Perps agentic flows: `hl-provision-fixture.json` (admin/test automation to flip HyperLiquid abstraction mode and transfer USDC between perps/spot via the exchange client) and `hl-balance-validation.json` (automation that refreshes account state, asserts `availableBalance` vs `availableToTradeBalance` invariants, and screenshots/validates balance text on market list, withdraw, and order entry screens). > > Extends `evals.json` with a reusable `hl-fixture-state` snapshot helper used by the new flows, and updates `perps-architecture.md` to link to the new HyperLiquid account-modes/portfolio-margin doc. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit b14ef5e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
|
|
✅ E2E Fixture Validation — Schema is up to date |
|
Closing in favor of starting new branch to handle through spotState websocket subscription. |




Description
HyperLiquid users in Unified Account Mode whose collateral is spot USDC (
withdrawable = 0, spot USDC > 0) can't place orders in MetaMask Mobile even though Phantom and HL web correctly treat the same account as tradeable. Incident: TAT-3016.What the fix does
AccountState.availableToTradeBalance?: string = withdrawable + getSpotBalance(spotState)(USDC + USDH viaSPOT_COLLATERAL_COINSallowlist, USDC-only today).availableToTradeBalance ?? availableBalance(market-details balance row, Long/Short CTA vs Add-Funds CTA, order-form submit gate).availableBalance = withdrawableon the withdraw path. Critical non-regression — users must never see the spot fold offered as withdrawable.availableToTradeBalance = availableBalancefield.Matches HyperLiquid's documented unified account / portfolio margin integration pattern: reconcile
clearinghouseStatewithspotClearinghouseState, keepavailableBalanceas raw withdrawable, use synthesizedavailableToTradeBalancefor order-entry funding decisions. Also matches observed behaviour in Phantom.Product assumption (explicit)
Correct for the current USDC-only market surface and acceptable for current pre-alpha scope. Revisit when HIP-3 / non-USDC markets roll out — tracked as TAT-3047.
Shape of the change
app/controllers/perps/types/index.ts— new optionalavailableToTradeBalancefield onAccountState.app/controllers/perps/utils/accountUtils.ts—addSpotBalanceToAccountStatenow bumps bothtotalBalanceandavailableToTradeBalanceusinggetSpotBalance(USDC + USDH allowlist). Wrapper helpers deleted.app/controllers/perps/utils/hyperLiquidAdapter.ts,providers/HyperLiquidProvider.ts,services/HyperLiquidSubscriptionService.ts— call sites updated to the folded helper; WebSocket#hashAccountStateincludes the new field so subscribers re-notify on change.app/components/UI/Perps/hooks/useDefaultPayWithTokenWhenNoPerpsBalance.ts— balance gate readsavailableToTradeBalance ?? availableBalance. One internal helper + three tiny exports (useHasNativeTradeablePerpsBalance,usePreferredFallbackPayTokenCandidate,arePaymentTokensEqual) consumed byuseInitPerpsPaymentToken.app/components/UI/Perps/Views/PerpsOrderView/useInitPerpsPaymentToken.ts— simple inference: clear saved token iffhasNativeTradeablePerpsBalance && arePaymentTokensEqual(savedToken, fallbackCandidate); else restore. No state machine, noselectedPaymentTokenSourcetag.app/components/UI/Perps/hooks/usePerpsOrderForm.ts,Views/PerpsOrderView/PerpsOrderView.tsx,hooks/usePerpsPaymentTokens.ts,hooks/usePerpsBalanceTokenFilter.ts,components/PerpsMarketBalanceActions/PerpsMarketBalanceActions.tsx,Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx— consume the fallback pattern.app/components/UI/Perps/Views/PerpsWithdrawView/PerpsWithdrawView.tsx+Perps.testIds.ts— addsperps-withdraw-available-balance-texttestID on the displayed balance so automation can assert the withdraw path does not leak the fold.app/controllers/perps/providers/HyperLiquidProvider.ts— newgetExchangeClient()public escape hatch used by admin/test flows (e.g. drivinguserSetAbstraction/usdClassTransferfrom the agentic recipe). Not on thePerpsProviderinterface.Validation harness
scripts/perps/agentic/teams/perps/flows/:hl-balance-validation— capturesAccountState+PerpsMarketListViewbalance text +PerpsWithdrawViewbalance text +PerpsOrderViewPay-with symbol with deterministicwait_forgates. Asserts fold invariant and no-fold-leak on withdraw.hl-provision-fixture— wrapsexchangeClient.userSetAbstractionandexchangeClient.usdClassTransferso recipes can cycle HL abstraction modes programmatically.Plus
hl-fixture-stateeval ref inscripts/perps/agentic/teams/perps/evals.jsonexposing the shared balance snapshot. Every timing gate iswait_forpolling a deterministic condition — no arbitrarywait: Nms.Out of scope (deliberate)
availableBalance → spendableBalance/withdrawableBalance) — TAT-3047.availableBalancesemantics preserved.availableToTradeBalance = availableBalancefield.@metamask/perps-controllerchanges.selectedPaymentTokenreset — pre-existing bug discovered during validation, filed as TAT-3050.Known behavioural change
totalBalanceon HyperLiquidAccountStatenow reflects withdrawable + allowlisted spot (USDC + USDH) only, not the sum of all spot coin totals. Non-stable spot holdings (e.g. HYPE) no longer contribute to the displayed total on perps surfaces. This matches the fold allowlistSPOT_COLLATERAL_COINS. Worth confirming with product if portfolio totals were relied on to include non-collateral spot.Changelog
CHANGELOG entry: Fixed Perps order entry for HyperLiquid Unified Mode users whose collateral is spot USDC.
Related issues
Fixes: TAT-3016
Context:
Manual testing steps
The useful validation is automated. Run
bash scripts/perps/agentic/validate-recipe.sh <artifacts-dir>with the recipe below against the shared Trading account (currently in the exact "Unified Mode + spot-only" state that triggered the incident).Observed values on the shared Trading account (last run: 105/105 PASS):
availableBalanceavailableToTradeBalanceIdentical balances across Unified / Standard flips are intentional per the product assumption above.
Screenshots/Recordings
Before
User cannot place orders on HL Unified Mode account with spot USDC collateral — the balance card displays
$0and the order form auto-selects an external USDC pay token with submit disabled.After
Three-phase cycle evidence. Each phase captures market-list balance (shows fold), withdraw balance (shows withdrawable-only), and order-form Pay-with (shows Perps balance by default).
Pre-merge author checklist
Performance checks (if applicable)
Pre-merge reviewer checklist
Note
Medium Risk
Changes how perps buying power is computed and propagated (new
availableToTradeBalance, spot hold handling, subscription hashing), which can affect order-entry gating and displayed balances across providers and UI surfaces.Overview
Fixes HyperLiquid unified-mode accounts where
withdrawableis0but spot USDC collateral exists by introducingAccountState.availableToTradeBalanceand using it (fallbacking toavailableBalance) to gate order-entry flows.Controllers/adapters now compute tradeable balance by adding supported spot collateral (USDC) minus
holdto perps withdrawable, aggregate it across DEX subaccounts, and include it in subscription hashing so UI updates when it changes; MYX returns a trivialavailableToTradeBalance = availableBalance. UI hooks/views (market details CTA, order form validation/max, payment token lists/filtering, balance header) now prefer this tradeable balance while keeping withdraw flows pinned toavailableBalance, with additional test coverage and a new withdraw-screentestIDto assert the invariant.Reviewed by Cursor Bugbot for commit 3c56fde. Bugbot is set up for automated code reviews on this repo. Configure here.