Skip to content

feat(perps): add lightweight position display on token details page#25685

Merged
abretonc7s merged 28 commits into
mainfrom
feat/perps/readonly-positions
Feb 9, 2026
Merged

feat(perps): add lightweight position display on token details page#25685
abretonc7s merged 28 commits into
mainfrom
feat/perps/readonly-positions

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Description

This PR optimizes the token details page by implementing a lightweight way to display open perps positions without requiring full PerpsController initialization, and adds one-click Long/Short trading buttons that navigate directly to the order confirmation flow.

Problem: Previously, checking if a user had an open position on a token required initializing the entire PerpsController with WebSocket subscriptions, causing slow page loads. Additionally, cached position data could become stale when users closed positions in the perps environment. There was also no way to initiate a perps trade directly from the token details page.

Solution: Added a readOnly mode pattern that allows querying perps data (positions, account state, markets) via lightweight HTTP API calls without WebSocket, wallet, or full controller initialization. Combined with a PerpsCacheInvalidator service that ensures cached data is refreshed when positions change. Added Long/Short buttons on token details that navigate through a new PerpsOrderRedirect screen to handle WebSocket initialization and order creation seamlessly.

Key Changes

New Hook:

  • usePerpsPositionForAsset - Fetches position data via single HTTP API call with 30-second client-side cache, subscribes to cache invalidation events

ReadOnly Mode (Controller/Provider):

  • getPositions({ readOnly: true, userAddress }) - Query positions without full init
  • getAccountState({ readOnly: true, userAddress }) - Query account state without full init
  • Uses createStandaloneInfoClient for lightweight HTTP-only queries

Cache Invalidation Service:

  • PerpsCacheInvalidator - Singleton service for loosely-coupled cache invalidation
  • Hooks subscribe to invalidation events (positions, accountState)
  • Services (TradingService, AccountService) call invalidate() after successful operations
  • Ensures position display is always accurate even after user actions in perps

One-Click Trade from Token Details:

  • New PerpsOrderRedirect screen - a redirect/loader that initializes the WebSocket connection inside the Perps stack, calls depositWithOrder(), then navigates to the confirmation screen
  • usePerpsActions hook - provides handlePerpsAction(direction) callback for Long/Short buttons, navigates to PerpsOrderRedirect with direction and asset params
  • AssetOverviewContent integrates Long/Short buttons via usePerpsActions when a perps market exists for the token

Dynamic Button Layout:

  • Token details action buttons (Buy, Sell, Swap, Bridge, etc.) now include Long/Short when a perps market exists
  • Buttons dynamically overflow into a "More" menu when there are too many actions

Documentation:

  • Added "ReadOnly Mode (Lightweight Queries)" section to docs/perps/perps-architecture.md
  • Added "Cache Invalidation" subsection documenting the pattern

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    PerpsCacheInvalidator                        │
│  (Singleton service)                                            │
├─────────────────────────────────────────────────────────────────┤
│  - invalidate('positions')    → notifies position subscribers   │
│  - invalidate('accountState') → notifies account subscribers    │
│  - subscribe(type, callback)  → returns unsubscribe function    │
└─────────────────────────────────────────────────────────────────┘
          ▲                                    ▲
          │ subscribe                          │ invalidate
          │                                    │
┌─────────┴─────────┐              ┌──────────┴──────────┐
│ usePerpsPosition  │              │   TradingService    │
│   ForAsset        │              │  (closePosition,    │
│                   │              │   placeOrder)       │
│ (30s cache +      │              │                     │
│  invalidation)    │              │   AccountService    │
└───────────────────┘              │  (withdraw)         │
                                   └─────────────────────┘

Order Redirect Flow (Long/Short buttons):
┌────────────────┐    navigate     ┌──────────────────┐   WS ready    ┌─────────────────┐
│  Token Details │ ──────────────→ │PerpsOrderRedirect│ ────────────→ │  Confirmation   │
│  (Long/Short)  │                 │  (Perps stack)   │  depositWith  │    Screen       │
│                │                 │  - init WebSocket │  Order()      │                 │
└────────────────┘                 │  - show loader    │               └─────────────────┘
                                   └──────────────────┘
                                          │ on error
                                          ▼
                                   goBack() + toast

Changelog

CHANGELOG entry: Added lightweight position display and one-click Long/Short trading on token details page for perps-enabled assets

Related issues

Fixes: TAT-2427

Manual testing steps

Feature: Perps position display on token details

  Scenario: User views token with open perps position
    Given user has an open perps position for ETH
    And user is on the wallet home screen

    When user navigates to ETH token details page
    Then user should see their open position displayed
    And the page should load quickly without full perps initialization

  Scenario: User taps on position to view details
    Given user is viewing token details with an open position displayed

    When user taps on the position card
    Then user should be navigated to the perps market detail page
    And the full perps environment should load

  Scenario: User with no perps position
    Given user does not have any perps positions

    When user navigates to any token details page
    Then no perps position card should be displayed
    And no perps API calls should be made (if user is not eligible)

  Scenario: Position display updates after closing position
    Given user has an open perps position for ETH
    And user is viewing the ETH token details page
    And user sees their open position displayed

    When user navigates to perps and closes their ETH position
    And user navigates back to the ETH token details page
    Then the position card should no longer be displayed
    And the cache should have been invalidated automatically

  Scenario: User taps Long button on token with perps market
    Given user is viewing ETH token details
    And ETH has a perps market available

    When user taps the "Long" button
    Then a loader screen should appear ("Preparing order...")
    And the WebSocket connection should initialize
    And depositWithOrder should be called
    And user should be navigated to the order confirmation screen

  Scenario: User taps Short button on token with perps market
    Given user is viewing ETH token details
    And ETH has a perps market available

    When user taps the "Short" button
    Then a loader screen should appear ("Preparing order...")
    And the WebSocket connection should initialize
    And depositWithOrder should be called
    And user should be navigated to the order confirmation screen

  Scenario: Long/Short buttons not shown for token without perps market
    Given user is viewing a token details page for a token without a perps market (e.g. USDC)

    Then no Long or Short buttons should be displayed in the action bar

  Scenario: Order redirect handles error gracefully
    Given user taps Long or Short on a perps-enabled token
    And the depositWithOrder call fails

    Then user should see an error toast notification
    And user should be navigated back to the token details page

Screenshots/Recordings

Before

After

Simulator.Screen.Recording.-.iPhone16Pro-Delta.-.2026-02-06.at.15.22.47.mp4

Pre-merge author checklist

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.

Note

Medium Risk
Introduces new read-only provider/controller code paths and cache invalidation wiring, plus a new navigation redirect that triggers depositWithOrder; mistakes could lead to stale UI or incorrect trade/confirmation routing, but changes are scoped and covered by new tests.

Overview
Adds a read-only perps discovery path so token details can query perps markets/positions/accountState via lightweight HTTP (no full controller/WebSocket init) by extending PerpsController/HyperLiquidProvider with readOnly + userAddress params and a standalone info client.

Introduces PerpsCacheInvalidator (and platform dependency plumbing) and wires it into TradingService and AccountService to invalidate read-only caches after successful state-changing actions, enabling hooks like the new usePerpsPositionForAsset (30s TTL + invalidation subscriptions) to stay fresh.

Updates token details (AssetOverviewContent) to display either a compact PerpsPositionCard or a PerpsDiscoveryBanner based on the read-only position lookup, adds geo-eligibility modal handling for Long/Short, and implements a one-click trade flow via new PerpsOrderRedirect route/screen that waits for perps connection, calls depositWithOrder, then StackActions.replaces into redesigned confirmations (or toasts + goes back on failure).

Written by Cursor Bugbot for commit 4a032ad. This will update automatically on new commits. Configure here.

abretonc7s and others added 4 commits February 5, 2026 09:33
Add usePerpsPositionForAsset hook that fetches position data via HTTP-only
API call with 30s client-side cache, avoiding full PerpsController
initialization. The full perps environment loads only when user taps
the position to view market details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@abretonc7s abretonc7s requested review from a team as code owners February 5, 2026 02:06
@github-actions

github-actions Bot commented Feb 5, 2026

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.

@metamaskbot metamaskbot added the team-perps Perps team label Feb 5, 2026
@abretonc7s abretonc7s changed the title Feat/perps/readonly positions feat(perps): add lightweight position display on token details page Feb 5, 2026
Comment thread app/components/UI/TokenDetails/components/AssetOverviewContent.tsx Outdated
Comment thread app/components/UI/Perps/controllers/PerpsController.ts
Comment thread app/components/UI/Perps/controllers/providers/HyperLiquidProvider.ts Outdated
abretonc7s and others added 2 commits February 5, 2026 10:29
Add PerpsCacheInvalidator service to ensure cached position data stays
fresh when users perform actions in perps environment.

- Create PerpsCacheInvalidator singleton service with subscribe/invalidate API
- Hook usePerpsPositionForAsset subscribes to positions and accountState events
- TradingService calls invalidate on placeOrder and closePosition success
- AccountService calls invalidate on withdraw success
- Add comprehensive unit tests (23 for service, 6 for hook integration)
- Document pattern in perps-architecture.md

This ensures token details page shows accurate position status even after
user closes positions in perps, while maintaining 30s cache performance.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use activeProviderInstance when available for readOnly mode queries,
with HyperLiquidProvider fallback for pre-initialization discovery.
This respects the provider abstraction while maintaining functionality.

- Extract getReadOnlyClearinghouseState helper in HyperLiquidProvider
- Add loading state handling in AssetOverviewContent
- Update test descriptions to match new behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread app/components/UI/Perps/controllers/services/TradingService.ts Outdated
Add PerpsCacheInvalidator.invalidate() calls for operations that modify
position data but were missing cache invalidation:

- updateMargin: margin changes affect position leverage/liquidation price
- flipPosition: bypasses placeOrder, needs explicit invalidation
- closePositions (batch): native batch close path was missing invalidation

This ensures usePerpsPositionForAsset hook shows fresh data after these
operations instead of waiting for 30-second cache expiry.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread slack.txt Outdated
Comment thread TASK_positions_in_assets.md Outdated
Replace direct import of PerpsCacheInvalidator singleton in controller
services with dependency injection via PerpsPlatformDependencies.

This maintains loose coupling and allows the controller/services to be
extracted to core without mobile-specific dependencies.

Changes:
- Add PerpsCacheInvalidator interface to PerpsPlatformDependencies
- Add InvalidateCacheParams type for method parameters
- Implement adapter in mobileInfrastructure.ts
- Update TradingService and AccountService to use this.deps.cacheInvalidator
- Update test mocks to include cacheInvalidator

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@abretonc7s abretonc7s force-pushed the feat/perps/readonly-positions branch from c9da792 to 4d8d087 Compare February 5, 2026 04:02
Comment thread app/components/UI/Perps/hooks/usePerpsPositionForAsset.ts
Comment thread app/components/UI/Perps/hooks/usePerpsPositionForAsset.ts
Comment thread app/components/UI/TokenDetails/components/AssetOverviewContent.tsx Outdated
Comment thread app/components/UI/TokenDetails/components/AssetOverviewContent.tsx
…ositions

# Conflicts:
#	app/components/UI/AssetOverview/TokenOverview.testIds.ts
#	app/components/UI/Perps/controllers/PerpsController.test.ts
#	app/components/UI/TokenDetails/components/AssetOverviewContent.tsx
@abretonc7s abretonc7s enabled auto-merge February 6, 2026 07:24
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread app/components/UI/Perps/Views/PerpsOrderRedirect.tsx
Comment thread app/components/UI/Perps/Views/PerpsOrderRedirect.tsx
@abretonc7s abretonc7s added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label Feb 6, 2026
Comment thread app/components/UI/Perps/components/PerpsPositionCard/PerpsPositionCard.tsx Outdated
Comment thread app/components/UI/TokenDetails/hooks/usePerpsActions.ts
abretonc7s and others added 5 commits February 6, 2026 23:43
## **Description**

Adds geolocalisation check on long and short buttons

## **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: Added geolocalisation check on long/short buttons on
token details page

## **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] -->


https://github.com/user-attachments/assets/99417619-cd37-41a1-bfa4-df16f6c8f3ca


## **Pre-merge author checklist**

- [ ] 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).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] 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.

## **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.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches token-details primary action flow and navigation locking;
regressions could block Long/Short actions or leave buttons locked, but
changes are localized and covered by unit tests.
> 
> **Overview**
> Adds a perps geolocation/eligibility gate to Token Details
**Long/Short** actions: ineligible users now see a
`PerpsBottomSheetTooltip` geo-block modal and an analytics event
(`MetaMetricsEvents.PERPS_SCREEN_VIEWED`) is tracked instead of
navigating/starting the perps flow.
> 
> Updates `TokenDetailsActions` to expose a parent-controlled reset for
its navigation lock so the UI can be unlocked after a non-navigating
modal is dismissed, and adds unit tests validating eligible vs
ineligible behavior.
> 
> Adjusts `PerpsOrderRedirect` loader presentation to match the perps
loading skeleton layout (wrap in `Box`, use `fullScreen={false}`) and
updates its test accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6d912e2. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@github-actions

github-actions Bot commented Feb 9, 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: 85%
click to see 🤖 AI reasoning details

E2E Test Selection:
This PR introduces a "one-click trade" feature allowing users to initiate perps trades directly from the Token Details page. The changes span multiple areas:

Core Controller Changes (CRITICAL):

  • PerpsController.ts: Added readOnly mode to getPositions() and getAccountState() methods, enabling lightweight queries without full perps initialization
  • HyperLiquidProvider.ts: Implemented readOnly mode with standalone HTTP clients for position/account queries

New Components/Services:

  • PerpsOrderRedirect.tsx: New redirect screen handling navigation from Token Details to Perps order confirmation
  • PerpsCacheInvalidator.ts: New cache invalidation service for readOnly query caches
  • usePerpsPositionForAsset.ts: New hook to check user's open perps positions for specific assets

UI Changes:

  • AssetOverviewContent.tsx: Added perps position card display, geo-block modal, updated Long/Short button handlers
  • PerpsPositionCard.tsx: Added compact mode for Token Details display
  • TokenDetailsActions.tsx: Added navigation lock reset for geo-block modal

Navigation:

  • New ORDER_REDIRECT route added to Routes.ts
  • PerpsOrderRedirect screen added to Perps navigation stack
  • usePerpsActions.ts changed to navigate to order redirect instead of market details

Tag Selection Rationale:

  1. SmokePerps: Required - Direct changes to PerpsController, HyperLiquidProvider, trading services, and perps views
  2. SmokeWalletPlatform: Required per tag description - "Perps is also a section inside the Trending tab; changes to Perps views (headers, lists, full views) affect Trending"
  3. SmokeConfirmations: Required per SmokePerps description - "Add Funds deposits are on-chain transactions" and the new PerpsOrderRedirect navigates to confirmation screens

The changes are well-tested with unit tests and follow established patterns (readOnly mode, cache invalidation). Risk is medium because while the controller changes are significant, they add new functionality without modifying existing critical paths.

Performance Test Selection:
The changes introduce new caching mechanisms (PerpsCacheInvalidator, usePerpsPositionForAsset with module-level cache), readOnly mode for position/account queries, and new UI components (PerpsPositionCard compact mode). These changes could impact perps loading performance, particularly: 1) Position data fetching with new readOnly mode, 2) Cache invalidation patterns affecting data refresh, 3) New hook (usePerpsPositionForAsset) with parallel API calls. The @PerformancePreps tag covers perps market loading, position management, and add funds flow - all areas affected by these changes.

View GitHub Actions results

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

readOnly: true,
userAddress,
}),
]);

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.

Duplicate API calls for same clearinghouse state data

Medium Severity

The usePerpsPositionForAsset hook calls getPositions and getAccountState in parallel via Promise.all, but both readOnly paths internally call getReadOnlyClearinghouseState(userAddress), which hits the same clearinghouseState HTTP endpoint with identical parameters. The single response already contains both assetPositions (for positions) and marginSummary/withdrawable (for account state). This doubles HTTP requests on every cache miss or invalidation for each perps-enabled token details page.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

I can follow up with a separate PR for this 🙏

@sonarqubecloud

sonarqubecloud Bot commented Feb 9, 2026

Copy link
Copy Markdown

@abretonc7s abretonc7s added this pull request to the merge queue Feb 9, 2026
Merged via the queue into main with commit c602ef7 Feb 9, 2026
96 of 97 checks passed
@abretonc7s abretonc7s deleted the feat/perps/readonly-positions branch February 9, 2026 10:31
@github-actions github-actions Bot locked and limited conversation to collaborators Feb 9, 2026
@metamaskbot metamaskbot added the release-7.66.0 Issue or pull request that will be included in release 7.66.0 label Feb 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.66.0 Issue or pull request that will be included in release 7.66.0 size-XL skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants