Skip to content

fix(perps): suppress "User or API Wallet does not exist" Sentry noise from unfunded wallets#29972

Merged
abretonc7s merged 12 commits into
mainfrom
fix/perps-sentry-noise-user-api-wallet
May 15, 2026
Merged

fix(perps): suppress "User or API Wallet does not exist" Sentry noise from unfunded wallets#29972
abretonc7s merged 12 commits into
mainfrom
fix/perps-sentry-noise-user-api-wallet

Conversation

@abretonc7s

@abretonc7s abretonc7s commented May 11, 2026

Copy link
Copy Markdown
Contributor

Description

Hyperliquid creates user accounts server-side on first USDC deposit. Until then, every user-scoped exchange write (agentSetAbstraction, userSetAbstraction, setReferrer) rejects with User or API Wallet 0x... does not exist. and the catch in HyperLiquidProvider.#ensureUnifiedAccountEnabled was forwarding these benign rejections to Sentry on every Perps section open.

Last 14d on 7.75.1+4800:

Source pinned by event Additional Context: HyperLiquidProvider.method: ensureUnifiedAccountEnabled.

Fix: proactive gate. Probe infoClient.userNonFundingLedgerUpdates once (cheap, ~100 ms, non-throwing) before any user-scoped exchange write. The ledger is empty if and only if the wallet has never interacted with Hyperliquid — a necessary and sufficient precondition for the exchange writes to succeed. When empty, skip the migration / referral writes, fire Perp Account Setup with status: not_applicable, error_message: no_hl_account. Positive observations cached in PerpsSigningCache.walletRegistered; negative results re-probe on next entry so the gate self-heals once the user deposits.

isHyperLiquidUserNotFoundError remains as a safety net in three catches (ensureUnifiedAccountEnabled, ensureReferralSet, setReferralCode) for the small race window where the probe succeeded but the write still rejected. Unrelated SDK errors keep reaching logger.error.

Reproduced standalone

Confirmed with a node script (not committed) that drives the SDK directly with a freshly-generated EOA. Key signals:

  • exchangeClient.agentSetAbstraction({abstraction:'u'}) rejects with the exact Sentry string.
  • infoClient.userAbstraction is not the throw site — it returns 'default' for fresh wallets.
  • infoClient.userNonFundingLedgerUpdates returns [] for fresh wallets — a clean array-length discriminator (no string parsing).

Why this supersedes #29828

#29828 catches and classifies after the SDK rejects. This PR prevents the SDK call when we already know it will fail.

#29828 This PR
Exchange round-trip for unfunded wallets Full call Skipped
HL-side error logs Generated None
Coverage ensureUnifiedAccountEnabled only ensureUnifiedAccountEnabled + ensureReferralSet
New throw sites Need a new catch Existing probe gates them

Cherry-picked from #29828: isHyperLiquidUserNotFoundError helper, reason: 'no_hl_account' discriminator, STATUS.NOT_APPLICABLE constant.

Changelog

CHANGELOG entry: null

Related issues

Supersedes: #29828
Sentry: METAMASK-MOBILE-4XB5, METAMASK-MOBILE-4Q4M, METAMASK-MOBILE-4KC4

Manual testing steps

Feature: Unfunded wallets do not pollute Sentry with HL exchange-write rejections

  Scenario: Fresh wallet opens Perps without depositing
    Given the wallet has never deposited to Hyperliquid
    When the user opens the Perps section
    Then no Sentry event is captured with title "ApiRequestError: User or API Wallet ** does not exist."
    And one Segment "Perp Account Setup" event with status=not_applicable, error_message=no_hl_account is fired

  Scenario: Wallet funds during the session
    Given the gate previously deferred for this wallet
    When the user deposits USDC and re-enters Perps
    Then the unified account migration runs as before

  Scenario: Unrelated SDK error still surfaces
    Given the wallet is funded
    When agentSetAbstraction throws e.g. "Insufficient margin"
    Then logger.error fires as before
    And the Segment event has status=failed (not not_applicable)

Automated:

yarn jest \
  app/controllers/perps/providers/HyperLiquidProvider.test.ts \
  app/controllers/perps/services/TradingReadinessCache.test.ts \
  app/controllers/perps/utils/errorUtils.test.ts \
  --no-coverage
# 3 suites, 395 passed, 0 failed

yarn lint clean. tsc --noEmit clean.

Screenshots/Recordings

N/A — internal observability change. Verify via Sentry dashboard delta 24–48h after release.

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android — N/A (no runtime UX change; one cheap extra read on Perps init for unfunded wallets)
  • I've tested with a power user scenario — N/A
  • I've instrumented key operations with Sentry traces for production performance metrics — N/A; removes noise rather than adds traces

Pre-merge reviewer checklist

  • I've manually tested the PR
  • I confirm that this PR addresses all acceptance criteria

Note

Medium Risk
Touches Hyperliquid account setup and referral flows and adds new caching/gating logic; mistakes could defer migration/referral for some funded users, though the probe is designed to fail open and has test coverage.

Overview
Reduces Perps Sentry noise by proactively skipping Hyperliquid exchange writes when the wallet has no Hyperliquid account yet (no funding/ledger history), and instead tracking Perp Account Setup with status: not_applicable and error_message: no_hl_account.

Adds #isWalletOnHyperliquid (ledger probe + positive-only cache in PerpsSigningCache.walletRegistered) and uses it to gate #ensureUnifiedAccountEnabled and referral setup; also introduces isHyperLiquidUserNotFoundError as a safety-net classifier so these benign rejections are debug-logged rather than sent to Sentry.

Extends analytics constants with STATUS.NOT_APPLICABLE, enriches TradingReadinessCache entries with an optional reason, and adds focused unit tests covering the new gate, cache behavior, and error classification.

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

Hyperliquid rejects exchange writes (agentSetAbstraction, userSetAbstraction,
setReferrer) with "User or API Wallet 0x... does not exist." when the wallet
has never deposited USDC. The catch in #ensureUnifiedAccountEnabled was
forwarding these benign rejections to Sentry on every Perps section open
— ~530k events / ~100k users in 14d on 7.75.1
(METAMASK-MOBILE-4XB5 iOS, METAMASK-MOBILE-4Q4M Android).

Fix is proactive: probe infoClient.clearinghouseState (cheap, non-throwing,
returns zeros for unfunded wallets) before any user-scoped exchange write.
When every existence signal is empty (accountValue / withdrawable / positions),
skip the migration and referral writes and fire a not_applicable analytics
event. Positive observations are cached in PerpsSigningCache.walletRegistered;
negative results re-probe on next entry so the gate self-heals once the user
deposits.

isHyperLiquidUserNotFoundError classifier remains as a safety net in three
catches (ensureUnifiedAccountEnabled, ensureReferralSet, setReferralCode)
for races where the probe succeeded but the write still rejected. Unrelated
SDK errors still reach logger.error.

CHANGELOG entry: null
@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 11, 2026
@abretonc7s abretonc7s marked this pull request as ready for review May 11, 2026 12:03
@abretonc7s abretonc7s requested a review from a team as a code owner May 11, 2026 12:04
…ates

Replaces clearinghouseState string-zero parsing with a simple array-length
check on the deposit/withdraw ledger. A non-empty ledger is necessary and
sufficient for `agentSetAbstraction` / `userSetAbstraction` / `setReferrer`
to succeed.

Why: clearinghouseState returns balances as strings ("0.0", "0", "0.00",
"0e0"); a string-equality check against the canonical "0.0" would let
non-canonical zero formats slip through and bypass the gate. The ledger
endpoint returns a plain array — empty if and only if the wallet has never
interacted with Hyperliquid.

Cost: same single API call, ~100 ms.
@abretonc7s abretonc7s enabled auto-merge May 12, 2026 13:10
Comment thread app/controllers/perps/utils/errorUtils.ts
Comment thread app/controllers/perps/constants/eventNames.ts
gambinish
gambinish previously approved these changes May 12, 2026

@gambinish gambinish left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved, but please evaluate my two comments to determine if necessary

…e-user-api-wallet

# Conflicts:
#	app/controllers/perps/providers/HyperLiquidProvider.ts
#	app/controllers/perps/utils/errorUtils.test.ts
Replace regex with case-insensitive .includes() checks per reviewer
feedback — less brittle across mobile and extension error message
variants.
…etaMask/metamask-mobile into fix/perps-sentry-noise-user-api-wallet
@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
a58f0fa5 ? opus / claude 0 ungraded (-) $46.4620
Worker report

Comments Report — PR #29972

# Author File Triage Action
1 gambinish app/controllers/perps/utils/errorUtils.ts:67 REAL Replace regex with .includes() checks per reviewer suggestion — less brittle across mobile and extension error message variants
2 gambinish app/controllers/perps/constants/eventNames.ts:366 OUT OF SCOPE Segment schema is a separate repo concern, not a code change in this PR

Summary

  • Total comments: 2 (1 REAL, 0 FALSE POSITIVE, 1 OUT OF SCOPE)
  • Commit SHA for fixes: d5e8315
  • Files changed: app/controllers/perps/utils/errorUtils.ts
  • Recipe re-validation result: SKIPPED (HAS_RECIPE=no, RECIPE_SOURCE=pr-body-llm-no-recipe-detected)
  • Merge-main status: conflicts-resolved (HyperLiquidProvider.ts imports, errorUtils.test.ts test blocks)

@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
6fd0d750 ? opus / claude 0 ungraded $46.4620
Worker report

Comments Report — PR #29972

# Author File Triage Action
1 gambinish app/controllers/perps/utils/errorUtils.ts:95 REAL Fixed in d5e8315 — replaced regex with case-insensitive .includes() checks per reviewer suggestion
2 gambinish app/controllers/perps/constants/eventNames.ts:366 OUT OF SCOPE Segment schema is a separate repo concern, not a code change in this PR

Summary

  • Total comments: 2 (1 REAL, 0 FALSE POSITIVE, 1 OUT OF SCOPE)
  • Commit SHA for fixes: d5e8315 (prior run), 7947119 (test coverage)
  • Files changed: app/controllers/perps/utils/errorUtils.ts, app/controllers/perps/providers/HyperLiquidProvider.test.ts
  • Recipe re-validation result: SKIPPED (HAS_RECIPE=no, RECIPE_SOURCE=pr-body-llm-no-recipe-detected)
  • Merge-main status: clean (already up to date with origin/main)

@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
8983c281 ? opus / claude 0 ungraded $46.4620
Worker report

Comments Report — PR #29972

# Author File Triage Action
1 gambinish app/controllers/perps/utils/errorUtils.ts:95 REAL Fixed in d5e8315 — replaced regex with case-insensitive .includes() checks per reviewer suggestion
2 gambinish app/controllers/perps/constants/eventNames.ts:366 OUT OF SCOPE Segment schema is a separate repo concern, not a code change in this PR

Summary

  • Total comments: 2 (1 REAL, 0 FALSE POSITIVE, 1 OUT OF SCOPE)
  • Commit SHA for fixes: d5e8315 (prior run)
  • Files changed: app/controllers/perps/utils/errorUtils.ts
  • Recipe re-validation result: SKIPPED (HAS_RECIPE=no, RECIPE_SOURCE=pr-body-llm-no-recipe-detected)
  • Merge-main status: clean merge (1a633a7)

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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 1a633a7. Configure here.

Comment thread app/controllers/perps/providers/HyperLiquidProvider.ts
When the !isRegistered branch defers migration, the memoized
#ensureReadyPromise must be reset so subsequent #ensureReady calls
re-probe. Without this flag, unfunded wallets stay permanently
deferred until provider reconnect or app restart.

Sets #unifiedAccountSetupNeedsRetry = true in both
ensureUnifiedAccountEnabled and ensureReferralSet deferred branches.
@abretonc7s

Copy link
Copy Markdown
Contributor Author
Run Duration Model Nudges Grade Cost
63eb4e2a ? opus / claude 0 ungraded $46.4620
Worker report

Comments Report — PR #29972

# Author File Triage Action
1 gambinish app/controllers/perps/utils/errorUtils.ts:95 REAL Already fixed in d5e8315 — replaced regex with .includes() checks. Reply already posted by prior run.
2 gambinish app/controllers/perps/constants/eventNames.ts:366 OUT OF SCOPE Segment schema is a separate repo concern. Reply already posted by prior run.
3 cursor[bot] app/controllers/perps/providers/HyperLiquidProvider.ts:787 REAL Fixed in bbb282b — added #unifiedAccountSetupNeedsRetry = true in both deferred branches so #ensureReady resets its memoized promise.

Summary

  • Total comments: 3 (2 REAL, 0 FALSE POSITIVE, 1 OUT OF SCOPE)
  • Commit SHA for fixes: bbb282b (this run), d5e8315 (prior run)
  • Files changed: app/controllers/perps/providers/HyperLiquidProvider.ts
  • Recipe re-validation result: SKIPPED (HAS_RECIPE=no, RECIPE_SOURCE=pr-body-llm-no-recipe-detected)
  • Merge-main status: clean (already up to date)

@github-actions

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 modifies the Perps controller layer with targeted changes:

  1. errorUtils.ts: New isHyperLiquidUserNotFoundError() utility to detect unfunded wallet rejections from Hyperliquid API.

  2. TradingReadinessCache.ts: Adds WalletRegistrationState tracking to the signing cache singleton, adds reason discriminator to SigningOperationState, and new getWalletRegistered/setWalletRegistered methods. This is a structural change to the global signing cache used across the Perps provider lifecycle.

  3. HyperLiquidProvider.ts: Adds #isWalletOnHyperliquid() probe method and integrates it into #ensureUnifiedAccountEnabled() and #ensureReferralSet() to proactively skip doomed exchange writes for unfunded wallets. Also adds safety-net error handling for the "user not found" case in both methods.

  4. eventNames.ts: Adds NOT_APPLICABLE analytics status value.

SmokePerps is the primary tag since these changes directly affect the Perps trading readiness flow, account setup initialization, and Add Funds flow. The #ensureUnifiedAccountEnabled and #ensureReferralSet changes affect what happens when a user first opens Perps or attempts to trade.

SmokeWalletPlatform is required per SmokePerps tag description (Perps is a section inside the Trending tab).

SmokeConfirmations is required per SmokePerps tag description (Add Funds deposits are on-chain transactions).

The changes are scoped to the Perps controller and don't affect other wallet features (accounts, networks, swaps, browser, etc.), so other tags are not needed.

Performance Test Selection:
The TradingReadinessCache changes add a new wallet registration probe (userNonFundingLedgerUpdates API call) that runs during Perps initialization. This could affect the time-to-interactive for the Perps flow, particularly for new users who haven't funded their Hyperliquid account. The @PerformancePreps tag covers perps market loading and the add funds flow, which are directly affected by the initialization changes in HyperLiquidProvider.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

@abretonc7s abretonc7s added this pull request to the merge queue May 15, 2026
Merged via the queue into main with commit 66b4d06 May 15, 2026
179 of 183 checks passed
@abretonc7s abretonc7s deleted the fix/perps-sentry-noise-user-api-wallet branch May 15, 2026 17:21
@github-actions github-actions Bot locked and limited conversation to collaborators May 15, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.79.0 Issue or pull request that will be included in release 7.79.0 label May 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.79.0 Issue or pull request that will be included in release 7.79.0 size-L team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants