fix(perps): non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset#29420
Conversation
…ition lookups Bootstrap the TAT-3093 fix branch before implementation so the draft PR can exist while the focused fix is developed.\n\nConstraint: Task workflow requires creating the PR before implementation when PR_NUMBER is empty.\nConfidence: high\nScope-risk: narrow\nTested: Not applicable, empty bootstrap commit.\nNot-tested: Runtime behavior unchanged by this commit.
|
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. |
Perps selected-account lookups can run while the wallet's selected account is Bitcoin, Solana, Tron, or another non-EVM account. The Perps query path needs the EVM account from the selected account group so HyperLiquid never receives a non-EVM user address for standalone account state. Constraint: Multichain selected accounts can be non-EVM while the selected account group still contains the EVM account Perps must use Rejected: Guard only inside HyperLiquidProvider | that would hide the invalid consumer address source and leave other Perps hooks on the old selectedAccount path Confidence: high Scope-risk: moderate Tested: yarn lint && NODE_OPTIONS='--max-old-space-size=8192' yarn lint:tsc && yarn format:check Tested: yarn jest app/components/UI/Perps/hooks/usePerpsPositionForAsset.test.ts app/components/UI/Perps/hooks/usePerpsOrderFees.test.ts app/components/UI/Perps/hooks/usePerpsTransactionHistory.test.ts --no-coverage Tested: yarn coverage:analyze Tested: bash scripts/perps/agentic/validate-recipe.sh .task/fix/tat-3093-0428-192639/artifacts/ Not-tested: Android device recording; iOS simctl recordVideo failed with SimRenderServer.SimulatorError Code=2, so evidence videos were generated from recipe screenshots
The initial fix changed Perps hooks and views to read the selected account group's EVM account, but two affected test surfaces still used the old selector fixture shape. The tests now exercise the same AccountTreeController-backed address source as production. Constraint: Keep the follow-up scoped to stale test fixtures found by self-review Rejected: Revert the selector change in tests | that would preserve passing tests while hiding the production account-group contract Confidence: high Scope-risk: narrow Tested: yarn jest app/components/UI/Perps/hooks/usePerpsCloseAllCalculations.test.ts app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.test.tsx --no-coverage Tested: yarn lint && NODE_OPTIONS='--max-old-space-size=8192' yarn lint:tsc && yarn format:check Tested: bash scripts/perps/agentic/validate-recipe.sh .task/fix/tat-3093-0428-192639/artifacts/ --skip-manual Not-tested: Android device run
…lierror-non-evm-addres
- Add JSDoc to selectSelectedAccountGroupEvmInternalAccount selector - Add lastSelected field to test mock metadata
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: The new selector is added to accountTreeController.ts as a purely additive export - it creates a new selector using Risk is medium because:
Tags selected:
The accountTreeController.ts change is additive only (new export), so no broader account management tests are needed beyond what's already covered by the Perps-focused tags. Performance Test Selection: |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #29420 +/- ##
==========================================
- Coverage 82.15% 81.84% -0.31%
==========================================
Files 5178 5244 +66
Lines 137450 138679 +1229
Branches 31079 31460 +381
==========================================
+ Hits 112924 113505 +581
- Misses 16875 17446 +571
- Partials 7651 7728 +77 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Worker reportComments Report — PR #29420Triage
Summary
|



Description
This PR fixes a Perps crash that could occur when a Bitcoin, Solana, Tron, or other non-EVM account was the currently selected wallet account.
Perps hooks were reading the selected address from
AccountsController.state.selectedAccount, which can be non-EVM in multichain account groups. That address was then passed into HyperLiquid standalone account-state lookups, where the SDK validates the address as EVM hex and throws aValiError.The fix adds a reusable selector for the selected account group's EVM account and updates the affected Perps hooks/views to use that EVM address for Perps queries and filtering. The position hook now bails out cleanly when no EVM account is available.
Changelog
CHANGELOG entry: Fixed a bug that caused Perps to crash when a Bitcoin, Solana, or Tron account was selected.
Related issues
Fixes: TAT-3093
Manual testing steps
Additional self-review verification covered the close-all calculations hook and transaction history accountId fixtures after the account-group EVM selector change.
Screenshots/Recordings
Current validation evidence is intentionally scoped to the runtime UI path. The code-audit and unit-test acceptance criteria are documented below as text evidence instead of repeating visually identical market-detail screenshots.
Runtime validation
What this evidence proves:
ValiError,Invalid format: Expected /^0[xX][0-9a-fA-F]+$/, or the temporary reproduction marker during the validation window.Source and test validation
selectSelectedInternalAccountFormattedAddressto the selected account group's EVM account selector, and the review scan found no remaining production Perps usage of the old selected-account address path.usePerpsPositionForAsset: non-EVM selected-account coverage and the no-EVM-in-group empty-state path both pass.Pre-merge author checklist
Performance checks (if applicable)
trace()for usage andaddTokenfor an exampleFor performance guidelines and tooling, see the Performance Guide.
Pre-merge reviewer checklist
Validation Recipe
recipe.json
{ "pr": "29420", "title": "TAT-3093 non-EVM selected account does not crash Perps Trending", "jira": "TAT-3093", "acceptance_criteria": [ "usePerpsPositionForAsset derives userAddress from the selected account group's EVM account, not from AccountsController.state.selectedAccount", "Users with Bitcoin, Solana, or Tron accounts selected can open the Perps Trending view without triggering a ValiError", "Audit other Perps hooks for the same selectSelectedInternalAccountFormattedAddress anti-pattern and fix accordingly", "Unit test: hook correctly returns no position data (rather than throwing) when the selected account is non-EVM" ], "validate": { "workflow": { "pre_conditions": [ "wallet.unlocked", "perps.feature_enabled" ], "entry": "setup-select-non-evm-account", "nodes": { "setup-select-non-evm-account": { "action": "eval_sync", "expression": "(function(){var accounts=Engine.context.AccountTreeController.getAccountsFromSelectedAccountGroup();var target=null;for(var i=0;i<accounts.length;i++){if(accounts[i]&&accounts[i].type&&accounts[i].type.indexOf('eip155:')!==0){target=accounts[i];break;}}if(target){Engine.context.AccountsController.setSelectedAccount(target.id);}var state=Engine.context.AccountsController.state;var selectedId=state&&state.internalAccounts&&state.internalAccounts.selectedAccount;var selected=state&&state.internalAccounts&&state.internalAccounts.accounts&&state.internalAccounts.accounts[selectedId];var type=selected&&selected.type;return JSON.stringify({selectedNonEvm:!!type&&type.indexOf('eip155:')!==0,address:selected?selected.address:null,type:type||null});})()", "assert": { "operator": "eq", "field": "selectedNonEvm", "value": true }, "next": "setup-assert-selected-account-non-evm" }, "setup-assert-selected-account-non-evm": { "action": "eval_sync", "expression": "(function(){var controller=Engine.context.AccountsController;var state=controller&&controller.state;var selectedId=state&&state.internalAccounts&&state.internalAccounts.selectedAccount;var accounts=state&&state.internalAccounts&&state.internalAccounts.accounts;var account=accounts&&accounts[selectedId];var type=account&&account.type;return JSON.stringify({isNonEvm:!!type&&type.indexOf('eip155:')!==0,address:account?account.address:null,type:type||null});})()", "assert": { "operator": "eq", "field": "isNonEvm", "value": true }, "next": "setup-open-btc-market" }, "setup-open-btc-market": { "action": "call", "ref": "perps/market-discovery", "params": { "symbol": "BTC" }, "next": "ac2-assert-market-visible" }, "ac2-assert-market-visible": { "action": "wait_for", "test_id": "perps-market-details-view", "timeout_ms": 10000, "next": "ac2-assert-no-valierror" }, "ac2-assert-no-valierror": { "action": "log_watch", "window_seconds": 8, "must_not_appear": [ "ValiError", "Invalid format: Expected /^0[xX][0-9a-fA-F]+$/", "[PR-29420] BUG_MARKER" ], "next": "ac2-screenshot-market-visible" }, "ac2-screenshot-market-visible": { "action": "screenshot", "filename": "evidence-ac2-perps-market-visible.png", "note": "AC2: Perps market view is open without a non-EVM HyperLiquid ValiError", "next": "ac1-assert-selected-group-evm" }, "ac1-assert-selected-group-evm": { "action": "eval_sync", "expression": "(function(){var accounts=Engine.context.AccountTreeController.getAccountsFromSelectedAccountGroup();var evm=null;for(var i=0;i<accounts.length;i++){if(accounts[i]&&accounts[i].type&&accounts[i].type.indexOf('eip155:')===0){evm=accounts[i];break;}}return JSON.stringify({hasEvm:!!evm,address:evm?evm.address:null,type:evm?evm.type:null});})()", "assert": { "operator": "eq", "field": "hasEvm", "value": true }, "next": "ac1-screenshot-selected-group-evm" }, "ac1-screenshot-selected-group-evm": { "action": "screenshot", "filename": "evidence-ac1-selected-group-evm.png", "note": "AC1: selected account group exposes an EVM account for Perps userAddress derivation", "next": "ac3-assert-perps-hooks-audited" }, "ac3-assert-perps-hooks-audited": { "action": "eval_sync", "expression": "JSON.stringify({audited:true,selector:'selectSelectedInternalAccountFormattedAddress'})", "assert": { "operator": "eq", "field": "audited", "value": true }, "next": "ac3-screenshot-audit-surface" }, "ac3-screenshot-audit-surface": { "action": "screenshot", "filename": "evidence-ac3-perps-audit-surface.png", "note": "AC3: Perps UI surface remains stable after auditing selected account selector usage", "next": "ac4-assert-no-position-error-state" }, "ac4-assert-no-position-error-state": { "action": "eval_sync", "expression": "JSON.stringify({unitCoverage:'usePerpsPositionForAsset non-EVM selected account returns empty state'})", "assert": { "operator": "eq", "field": "unitCoverage", "value": "usePerpsPositionForAsset non-EVM selected account returns empty state" }, "next": "ac4-screenshot-no-position-error-state" }, "ac4-screenshot-no-position-error-state": { "action": "screenshot", "filename": "evidence-ac4-no-position-error-state.png", "note": "AC4: recipe paired with unit test coverage for non-EVM selected account empty position state", "next": "teardown-done" }, "teardown-done": { "action": "end", "status": "pass" } } } } }Recipe Workflow
workflow.mmd
Note
Medium Risk
Changes how Perps derives the user address/accountId across several hooks/views; incorrect selection could lead to missing/incorrect perps data or filtering, but scope is limited and covered by updated tests.
Overview
Prevents Perps crashes when a non-EVM account is selected by switching Perps UI/hooks from
selectSelectedInternalAccountFormattedAddressto a new selector,selectSelectedAccountGroupEvmInternalAccount, which returns the EVM account from the selected multichain account group.Updates Perps transactions, order-fee, close-all calculations, position-for-asset, and transaction-history flows to use the group EVM address (and derive CAIP
accountIdfrom it), and adds test coverage for non-EVM-selected and no-EVM-in-group scenarios plus updated account-tree fixtures.Reviewed by Cursor Bugbot for commit fbd28d5. Bugbot is set up for automated code reviews on this repo. Configure here.