Skip to content

fix(perps): non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset#29420

Merged
abretonc7s merged 7 commits into
mainfrom
fix/tat-3093-perps-valierror-non-evm-addres
May 4, 2026
Merged

fix(perps): non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset#29420
abretonc7s merged 7 commits into
mainfrom
fix/tat-3093-perps-valierror-non-evm-addres

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

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 a ValiError.

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

Feature: Perps with non-EVM selected account

  Scenario: User opens Perps while a non-EVM account is selected
    Given a multichain account group is selected
    And the currently selected account in the wallet is non-EVM

    When the user opens the Perps Trending view
    Then the Perps market list or market details screen opens
    And no HyperLiquid ValiError is logged for a non-EVM user address
    And Perps hooks use the account group's EVM address for Perps queries

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

Perps market opens with a non-EVM account selected
Perps BTC market details open while validating non-EVM selected account path
Selected account group exposes an EVM account
Recipe assertion for selected account group EVM account

What this evidence proves:

  • A non-EVM account was selected from the current multichain account group during validation.
  • The Perps BTC market detail screen opened successfully.
  • The recipe log watch did not find ValiError, Invalid format: Expected /^0[xX][0-9a-fA-F]+$/, or the temporary reproduction marker during the validation window.
  • The selected account group contained an EVM account used for Perps address derivation.

Source and test validation

  • AC3 is covered by source audit: the affected Perps hooks/views were migrated away from selectSelectedInternalAccountFormattedAddress to 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.
  • AC4 is covered by focused unit tests for usePerpsPositionForAsset: non-EVM selected-account coverage and the no-EVM-in-group empty-state path both pass.
  • Additional manual validation is being re-run to improve the evidence set before final review.

Pre-merge author checklist

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 to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

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.

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
flowchart TD
  %% TAT-3093 non-EVM selected account does not crash Perps Trending
  __entry__(["ENTRY"]) --> node_setup_select_non_evm_account
  node_setup_select_non_evm_account["setup-select-non-evm-account<br/>eval_sync"]
  node_setup_assert_selected_account_non_evm["setup-assert-selected-account-non-evm<br/>eval_sync"]
  node_setup_open_btc_market[["setup-open-btc-market<br/>perps/market-discovery"]]
  node_ac2_assert_market_visible["ac2-assert-market-visible<br/>wait_for"]
  node_ac2_assert_no_valierror["ac2-assert-no-valierror<br/>log_watch"]
  node_ac2_screenshot_market_visible["ac2-screenshot-market-visible<br/>screenshot"]
  node_ac1_assert_selected_group_evm["ac1-assert-selected-group-evm<br/>eval_sync"]
  node_ac1_screenshot_selected_group_evm["ac1-screenshot-selected-group-evm<br/>screenshot"]
  node_ac3_assert_perps_hooks_audited["ac3-assert-perps-hooks-audited<br/>eval_sync"]
  node_ac3_screenshot_audit_surface["ac3-screenshot-audit-surface<br/>screenshot"]
  node_ac4_assert_no_position_error_state["ac4-assert-no-position-error-state<br/>eval_sync"]
  node_ac4_screenshot_no_position_error_state["ac4-screenshot-no-position-error-state<br/>screenshot"]
  node_teardown_done(["teardown-done<br/>PASS"])
  node_setup_select_non_evm_account --> node_setup_assert_selected_account_non_evm
  node_setup_assert_selected_account_non_evm --> node_setup_open_btc_market
  node_setup_open_btc_market --> node_ac2_assert_market_visible
  node_ac2_assert_market_visible --> node_ac2_assert_no_valierror
  node_ac2_assert_no_valierror --> node_ac2_screenshot_market_visible
  node_ac2_screenshot_market_visible --> node_ac1_assert_selected_group_evm
  node_ac1_assert_selected_group_evm --> node_ac1_screenshot_selected_group_evm
  node_ac1_screenshot_selected_group_evm --> node_ac3_assert_perps_hooks_audited
  node_ac3_assert_perps_hooks_audited --> node_ac3_screenshot_audit_surface
  node_ac3_screenshot_audit_surface --> node_ac4_assert_no_position_error_state
  node_ac4_assert_no_position_error_state --> node_ac4_screenshot_no_position_error_state
  node_ac4_screenshot_no_position_error_state --> node_teardown_done
Loading

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 selectSelectedInternalAccountFormattedAddress to 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 accountId from 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.

…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.
@abretonc7s abretonc7s added DO-NOT-MERGE Pull requests that should not be merged agentic labels Apr 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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.

@metamaskbotv2 metamaskbotv2 Bot added the team-perps Perps team label Apr 28, 2026
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
@abretonc7s abretonc7s marked this pull request as ready for review April 30, 2026 11:02
@abretonc7s abretonc7s requested review from a team as code owners April 30, 2026 11:02
@abretonc7s abretonc7s enabled auto-merge April 30, 2026 11:48
@abretonc7s abretonc7s removed the DO-NOT-MERGE Pull requests that should not be merged label May 4, 2026
@abretonc7s abretonc7s changed the title fix(perps): [Perps] ValiError: non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset fix(perps): ValiError: non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset May 4, 2026
Comment thread app/selectors/multichainAccounts/accountTreeController.ts
abretonc7s added 2 commits May 4, 2026 17:42
- Add JSDoc to selectSelectedAccountGroupEvmInternalAccount selector
- Add lastSelected field to test mock metadata
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePreps
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR makes a focused change across the Perps feature area: replacing selectSelectedInternalAccountFormattedAddress (returns a formatted address string) with the new selectSelectedAccountGroupEvmInternalAccount selector (returns the full EVM InternalAccount object, then accesses .address). This affects 4 Perps hooks (usePerpsCloseAllCalculations, usePerpsOrderFees, usePerpsPositionForAsset, usePerpsTransactionHistory) and PerpsTransactionsView.

The new selector is added to accountTreeController.ts as a purely additive export - it creates a new selector using createSelector on top of selectSelectedAccountGroupInternalAccounts, filtering for EVM account types. No existing selectors are modified.

Risk is medium because:

  1. The selector swap changes the data source for address resolution in Perps - if selectSelectedAccountGroupEvmInternalAccount returns null in scenarios where the old selector returned an address, Perps functionality (position tracking, order fees, transaction history, close-all calculations) would break.
  2. The change is specifically designed for multi-chain account group architecture compatibility, which is important for correctness.

Tags selected:

  • SmokePerps: Directly tests the affected Perps functionality (Add Funds, balance verification, position management)
  • SmokeWalletPlatform: Required by SmokePerps description since Perps is a section inside the Trending tab
  • SmokeConfirmations: Required by SmokePerps description since Add Funds deposits are on-chain transactions

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:
The changes affect core Perps hooks (usePerpsPositionForAsset, usePerpsOrderFees, usePerpsCloseAllCalculations, usePerpsTransactionHistory) and PerpsTransactionsView by swapping the account address selector. These hooks are used in perps market loading, position management, and order execution flows - all covered by @PerformancePreps. The selector change could affect re-render behavior since the new selector returns a full account object instead of just a string, potentially impacting memoization and render performance.

View GitHub Actions results

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.84%. Comparing base (51b6bbd) to head (fbd28d5).
⚠️ Report is 40 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

sonarqubecloud Bot commented May 4, 2026

Copy link
Copy Markdown

@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
8a71ce63 ? opus (claude) 0 ungraded (-) $unknown
Worker report

Comments Report — PR #29420

Triage

# Author File Triage Action
1 ccharly app/selectors/multichainAccounts/accountTreeController.ts:423 REAL Add JSDoc to selectSelectedAccountGroupEvmInternalAccount
2 ccharly app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.test.tsx:108 REAL Add lastSelected: 0 to mock metadata

Summary

  • Total comments: 2 (2 REAL, 0 FALSE POSITIVE, 0 OUT OF SCOPE)
  • Commit SHA: fbd28d5
  • Files changed: app/selectors/multichainAccounts/accountTreeController.ts, app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.test.tsx
  • Recipe re-validation: PASS (12/12 steps)
  • Merge-main status: clean (no conflicts)

@ccharly ccharly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code LGTM! (not tested)

@abretonc7s abretonc7s changed the title fix(perps): ValiError: non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset fix(perps): non-EVM address passed to HyperLiquid validator via usePerpsPositionForAsset May 4, 2026
@abretonc7s abretonc7s added this pull request to the merge queue May 4, 2026
Merged via the queue into main with commit 2e7fdfb May 4, 2026
109 checks passed
@abretonc7s abretonc7s deleted the fix/tat-3093-perps-valierror-non-evm-addres branch May 4, 2026 14:13
@github-actions github-actions Bot locked and limited conversation to collaborators May 4, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.77.0 Issue or pull request that will be included in release 7.77.0 label May 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

agentic release-7.77.0 Issue or pull request that will be included in release 7.77.0 size-M team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants