Skip to content

fix: [Mobile] Perps KEYRING_LOCKED error in HyperLiquidWalletService#30077

Merged
michalconsensys merged 8 commits into
mainfrom
fix/tat-3176-fix-perps-keyring-locked
May 13, 2026
Merged

fix: [Mobile] Perps KEYRING_LOCKED error in HyperLiquidWalletService#30077
michalconsensys merged 8 commits into
mainfrom
fix/tat-3176-fix-perps-keyring-locked

Conversation

@abretonc7s

@abretonc7s abretonc7s commented May 13, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes Perps KEYRING_LOCKED handling when HyperLiquid wraps wallet signing failures. The provider now walks the Error.cause chain so wrapped keyring-lock errors use the retry-later path instead of being reported as generic setup failures.

Follow-up considered: preflight-gating Perps signing setup until the keyring is unlocked/available. That may avoid entering SDK signing paths that cannot succeed, but it changes initialization timing and retry behavior. This PR keeps the existing retry-later flow and fixes the concrete error-boundary bug: HyperLiquid SDK wraps KEYRING_LOCKED as the error cause, so provider catches must inspect the cause chain. We will monitor Sentry after release; if locked signing events remain meaningful, the next step is a keyring-availability gate before signing-dependent setup.

Changelog

CHANGELOG entry: Fixed a Perps setup error that could report locked-keyring retries as failures

Related issues

Fixes: TAT-3176

Manual testing steps

Feature: Perps keyring lock retry

  Scenario: wrapped keyring lock is handled as retry-later
    Given HyperLiquid setup receives a wrapped signing error with cause.message KEYRING_LOCKED
    When the provider classifies the setup error
    Then the provider uses the keyring-locked retry-later branch
    And the generic Sentry failure branch is not used

Screenshots/Recordings

No visual evidence selected for publication.

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": "",
  "title": "TAT-3176 wrapped KEYRING_LOCKED classifier",
  "jira": "TAT-3176",
  "acceptance_criteria": [
    "Wrapped KEYRING_LOCKED errors whose cause.message is KEYRING_LOCKED are classified as keyring-locked retry-later errors instead of falling through to generic logger.error/Sentry, failed AccountSetup analytics, or failure caches."
  ],
  "validate": {
    "workflow": {
      "entry": "ac1-assert-wrapped-keyring-locked",
      "nodes": {
        "ac1-assert-wrapped-keyring-locked": {
          "action": "eval_sync",
          "expression": "(function(){var mods=globalThis.__r.getModules();var moduleId=null;mods.forEach(function(v,k){if(v&&v.verboseName==='app/controllers/perps/utils/errorUtils.ts'){moduleId=k;}});var errorUtils=globalThis.__r(moduleId);var cause=new Error('KEYRING_LOCKED');var wrapped=new Error('Failed to sign typed data with viem wallet');wrapped.cause=cause;return JSON.stringify({moduleFound:moduleId!==null,classified:errorUtils.isKeyringLockedError(wrapped)});})()",
          "assert": {
            "operator": "eq",
            "field": "classified",
            "value": true
          },
          "next": "done"
        },
        "done": {
          "action": "end",
          "status": "pass"
        }
      }
    }
  }
}

Recipe Workflow

workflow.mmd
flowchart TD
  ac1["ac1-assert-wrapped-keyring-locked"]
  done["done"]
  ac1 --> done
Loading

Note

Medium Risk
Changes error classification for keyring-locked signing failures, affecting retry/caching and whether failures get logged/reported; mistakes here could cause repeated prompts or suppressed retries in trading setup.

Overview
Prevents HyperLiquid SDK-wrapped signing failures from being treated as generic setup errors by introducing isKeyringLockedError() (walks the Error.cause chain) and using it in HyperLiquid unified account enablement, builder-fee approval, and referral setup.

Adds test coverage ensuring wrapped KEYRING_LOCKED errors do not populate failure caches (TradingReadinessCache/PerpsSigningCache) or trigger error logging, while still releasing in-flight locks and allowing the order/setup flow to proceed.

Reviewed by Cursor Bugbot for commit 4fdd74f. Bugbot is set up for automated code reviews on this repo. Configure here.

Constraint: HyperLiquid SDK preserves the original KEYRING_LOCKED error in cause while provider catches saw the wrapper message.

Rejected: Check only ensureError(error).message | misses AbstractWalletError-wrapped signing failures.

Confidence: high

Scope-risk: narrow

Directive: Keep perps signing setup catches using isKeyringLockedError so wrapped wallet failures stay retryable and out of generic Sentry paths.

Tested: yarn lint && NODE_OPTIONS='--max-old-space-size=8192' yarn lint:tsc && yarn format:check

Tested: yarn jest app/controllers/perps/utils/errorUtils.test.ts app/controllers/perps/providers/HyperLiquidProvider.test.ts --no-coverage

Tested: yarn coverage:analyze --files app/controllers/perps/utils/errorUtils.ts app/controllers/perps/providers/HyperLiquidProvider.ts

Tested: bash scripts/perps/agentic/validate-recipe.sh .task/fix/tat-3176-0512-222920/artifacts/ --skip-manual

Not-tested: Android device reproduction; fix is shared TypeScript and was validated with unit/state checks.
Constraint: Self-review found wrapped builder-fee KEYRING_LOCKED coverage inside a skipped describe.

Rejected: Keep private-method test under skipped cache describe | Jest would not execute the regression coverage.

Confidence: high

Scope-risk: narrow

Directive: Keep wrapped keyring-lock provider coverage on executed public paths.

Tested: yarn jest app/controllers/perps/providers/HyperLiquidProvider.test.ts --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-3176-0512-222920/artifacts/ --skip-manual

Not-tested: GitHub PR publication; branch remains local for gateway publishing.
Document why the keyring-locked catches inspect HyperLiquid SDK cause chains, so future edits do not reintroduce failure-cache or Sentry handling for retryable locked-keyring setup.

Constraint: HyperLiquid wraps wallet signing failures while preserving KEYRING_LOCKED in Error.cause

Rejected: replacing this with a preflight-only gate | races and SDK wrapping still require catch-time classification

Confidence: high

Scope-risk: narrow

Directive: Keep cause-chain classification on signing setup catches unless signing errors are normalized below the SDK boundary

Tested: git diff --check -- app/controllers/perps/providers/HyperLiquidProvider.ts .task/fix/tat-3176-0512-222920/artifacts/pr-description.md

Not-tested: Jest not rerun; comment-only code change
Clarify the Perps error utility module scope after adding a perps-specific keyring classification helper.

Constraint: Self-review requested the smallest possible documentation fix in the flagged file

Rejected: moving isKeyringLockedError elsewhere | broader refactor is unnecessary for a header mismatch

Confidence: high

Scope-risk: narrow

Directive: Keep the module header aligned with both generic helpers and perps-specific classifiers

Tested: yarn jest app/controllers/perps/utils/errorUtils.test.ts --no-coverage; yarn lint && NODE_OPTIONS='--max-old-space-size=8192' yarn lint:tsc && yarn format:check; bash scripts/perps/agentic/validate-recipe.sh .task/fix/tat-3176-0512-222920/artifacts/ --skip-manual

Not-tested: No manual device testing; comment-only fix
@abretonc7s abretonc7s requested a review from a team as a code owner May 13, 2026 04:33
@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 May 13, 2026
@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
1955915a ? gpt-5.5 0 medium / 4 $unknown
Worker report

TAT-3176 Report

Summary

Fixed wrapped KEYRING_LOCKED classification in HyperLiquid perps setup paths. The provider now walks the error cause chain so SDK-wrapped signing failures are treated as retry-later keyring-lock states instead of generic failures.

Root cause

HyperLiquidWalletService.#signTypedMessage throws KEYRING_LOCKED when the keyring is unavailable. The HyperLiquid SDK wraps that as Failed to sign typed data with viem wallet and stores the original error in cause, but HyperLiquidProvider compared only ensureError(error).message at the unified-account, builder-fee, and referral catches. That missed wrapped keyring-lock errors and let them fall through to failed analytics/cache/Sentry paths.

Reproduction commit

Marker commit: b9ff93030c1b7ec4250e5741a97bf35d47119cfd

Metro excerpt:

DEBUG  [PR-local] BUG_MARKER: wrapped KEYRING_LOCKED reached generic Sentry logger

Changes

  • app/controllers/perps/utils/errorUtils.ts: added isKeyringLockedError, a cycle-safe cause-chain classifier.
  • app/controllers/perps/providers/HyperLiquidProvider.ts: replaced three direct message equality checks with the cause-chain classifier.
  • app/controllers/perps/utils/errorUtils.test.ts: covered direct, wrapped, and cyclic error cases.
  • app/controllers/perps/providers/HyperLiquidProvider.test.ts: covered wrapped keyring-lock behavior in unified-account, builder-fee, and referral setup paths.

Test plan

  • yarn lint && NODE_OPTIONS='--max-old-space-size=8192' yarn lint:tsc && yarn format:check
  • yarn jest app/controllers/perps/utils/errorUtils.test.ts app/controllers/perps/providers/HyperLiquidProvider.test.ts --no-coverage
  • yarn coverage:analyze --files app/controllers/perps/utils/errorUtils.ts app/controllers/perps/providers/HyperLiquidProvider.ts
  • bash scripts/perps/agentic/validate-recipe.sh .task/fix/tat-3176-0512-222920/artifacts/ --skip-manual
Feature: Wrapped KEYRING_LOCKED handling

  Scenario: wrapped keyring lock is retried later
    Given HyperLiquid setup receives an SDK error with cause.message KEYRING_LOCKED
    When the provider classifies the setup error
    Then the retry-later branch is used
    And the generic Sentry failure branch is not used

Evidence

  • .task/fix/tat-3176-0512-222920/artifacts/recipe.json
  • .task/fix/tat-3176-0512-222920/artifacts/recipe-baseline.json
  • .task/fix/tat-3176-0512-222920/artifacts/recipe-coverage.md
  • .task/fix/tat-3176-0512-222920/artifacts/evidence-manifest.json
  • State-only fix; no before/after screenshots or videos are required.

Ticket

TAT-3176: https://consensyssoftware.atlassian.net/browse/TAT-3176

Self-Review Fixes

  • app/controllers/perps/providers/HyperLiquidProvider.test.ts — moved wrapped KEYRING_LOCKED builder-fee coverage out of the skipped global-cache describe and into an executed public placeOrder path assertion.
  • .task/fix/tat-3176-0512-222920/artifacts/recipe.json — changed the recipe to load the bundled app/controllers/perps/utils/errorUtils.ts module through Metro and call the real isKeyringLockedError export instead of duplicating classifier logic inline.
  • app/controllers/perps/utils/errorUtils.ts:1 — updated the module header so it accurately describes both generic error helpers and Perps error classification helpers.

Workflow Diagram (Mermaid)

flowchart TD
ac1["ac1-assert-wrapped-keyring-locked"]
done["done"]
ac1 --> done

Recipe workflow diagram
flowchart TD
  ac1["ac1-assert-wrapped-keyring-locked"]
  done["done"]
  ac1 --> done
Loading

Retrigger CI after unrelated iOS confirmations smoke failures on PR #30077; branch-local lint, typecheck, format, and recipe checks pass with no code changes needed.

Constraint: CI-FIX.md requires commit and push after triage; failed jobs are outside the Perps controller diff

Rejected: code change in confirmations smoke flow | failures are unrelated to TAT-3176 changed files and branch-relevant checks passed

Confidence: medium

Scope-risk: narrow

Directive: Re-evaluate if the same iOS smoke shards fail again after rerun

Tested: yarn lint && NODE_OPTIONS='--max-old-space-size=8192' yarn lint:tsc && yarn format:check; bash scripts/perps/agentic/validate-recipe.sh .task/fix/tat-3176-0512-222920/artifacts/ --skip-manual

Not-tested: iOS E2E smoke not rerun locally
@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
3e64f136 ? gpt-5.5 0 ungraded (-) $unknown
Worker report
# Author File Triage Action
1 github-actions[bot] conversation OUT OF SCOPE CLA status notification; no code action required.
2 abretonc7s conversation OUT OF SCOPE Worker report and validation context; confirms the PR intent and does not request changes.
3 github-actions[bot] conversation OUT OF SCOPE Smart E2E selection notification; no code action required.
4 sonarqubecloud[bot] conversation OUT OF SCOPE Sonar quality gate passed notification; no code action required.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 92%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are scoped to the Perps controller layer:

  1. errorUtils.ts: Adds isKeyringLockedError() — a new utility that walks the error cause chain to detect KEYRING_LOCKED errors even when wrapped by SDK layers (e.g., viem wallet signing failures).

  2. HyperLiquidProvider.ts: Replaces direct string comparison with isKeyringLockedError() in three catch blocks (ensureUnifiedAccountEnabled, ensureBuilderFeeApproval, ensureReferralSet). This is a bug fix: previously, SDK-wrapped KEYRING_LOCKED errors were not detected, causing retry caches to be incorrectly populated. Now the full error cause chain is checked.

  3. Test files: Unit tests validating the new utility and the HyperLiquidProvider behavior with wrapped errors.

Tag rationale:

  • SmokePerps: Directly affected — HyperLiquidProvider is the core perps trading provider. The fix affects order placement flows (builder fee approval, referral setup, unified account enabling) when the keyring is locked.
  • SmokeWalletPlatform: Required by SmokePerps description (Perps is a section inside Trending tab).
  • SmokeConfirmations: Required by SmokePerps description (Add Funds deposits are on-chain transactions).

No changes to UI components, navigation, shared infrastructure, or other controllers. Risk is medium because the fix touches core trading flow error handling in the perps provider.

Performance Test Selection:
No UI rendering changes, no list components, no state management changes, no app startup changes. The change is purely error handling logic (isKeyringLockedError utility) in the perps provider catch blocks. No performance impact expected.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

@michalconsensys michalconsensys added this pull request to the merge queue May 13, 2026
Merged via the queue into main with commit 8cfa941 May 13, 2026
180 of 181 checks passed
@michalconsensys michalconsensys deleted the fix/tat-3176-fix-perps-keyring-locked branch May 13, 2026 10:42
@github-actions github-actions Bot locked and limited conversation to collaborators May 13, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.78.0 Issue or pull request that will be included in release 7.78.0 label May 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants