Skip to content

fix(perps): pass position in TP/SL onConfirm to avoid No position found errors#27409

Merged
abretonc7s merged 8 commits into
mainfrom
fix/perps/tpsl-update-v2
Mar 30, 2026
Merged

fix(perps): pass position in TP/SL onConfirm to avoid No position found errors#27409
abretonc7s merged 8 commits into
mainfrom
fix/perps/tpsl-update-v2

Conversation

@michalconsensys

@michalconsensys michalconsensys commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Description

When editing Take Profit / Stop Loss (TP/SL) for an existing Perps position, the onConfirm callback relied on a ref (currentPositionRef.current) to get the position at execution time. If the position was updated during navigation (e.g. via WebSocket), the ref could be stale, causing "No position found" errors when the user confirmed TP/SL.

This change extends the TP/SL onConfirm signature to accept an optional first argument position. The TPSL view now passes the position from route params into onConfirm, and the market-details flow uses that passed position when present, falling back to the ref. Order flow and market-tabs callbacks were updated to match the new signature (they ignore the position argument). This ensures the callback always receives the correct position and avoids the stale-ref error.

Changelog

CHANGELOG entry: null

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2369

Manual testing steps

Feature: Perps TP/SL confirmation

  Scenario: user confirms TP/SL for an existing position without "No position found" error
    Given user has an open Perps position and navigates to edit TP/SL (market details or market tabs)
    And the position data may have been updated (e.g. via WebSocket) before user taps Confirm

    When user sets Take Profit and/or Stop Loss and taps Confirm

    Then TP/SL is updated successfully and no "No position found" error is shown

Screenshots/Recordings

N/A (behavioral fix; no UI change).

Before

After

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
Changes the PerpsTPSL route onConfirm callback signature/return type and updates multiple callers, so mismatches could break TP/SL confirmation flows if any usage is missed.

Overview
Fixes a TP/SL confirmation race by passing the position from PerpsTPSLView into the route onConfirm callback, and having PerpsMarketDetailsView prefer that position (falling back to currentPositionRef) before calling handleUpdateTPSL.

Updates the PerpsTPSL navigation type and all known callers (PerpsOrderView, PerpsMarketTabs) to match the new (position?, tp?, sl?, trackingData?) signature, adjusts TPSL view tests accordingly, and adds a market-details test asserting { success: false } when confirm runs after the position is cleared.

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

@metamaskbot metamaskbot added the team-perps Perps team label Mar 12, 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.

@michalconsensys michalconsensys marked this pull request as ready for review March 12, 2026 09:56
@michalconsensys michalconsensys requested a review from a team as a code owner March 12, 2026 09:56
gambinish
gambinish previously approved these changes Mar 17, 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.

Approving, but if you have time, could we increase test coverage here?

@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk size-M and removed size-S labels Mar 18, 2026
@abretonc7s

abretonc7s commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Automated Review — PR #27409

BETA — Automated review from the farmslot pipeline.

Recommendation APPROVE
Runner claude / sonnet
Tier full
Cost $3.1011 (7.2M tokens)
Recipe 15 steps defined

Summary

The PR fixes an intermittent "No position found" error when confirming TP/SL on an existing position. The root cause was that the onConfirm callback in PerpsMarketDetailsView captured the position via currentPositionRef.current at execution time. If the position was updated by a WebSocket message between navigation and confirm, the ref could hold the updated state while the closure-captured checks didn't keep pace, leading to null/stale data.

The fix passes the position from PerpsTPSLView's route params into onConfirm as a first argument, and PerpsMarketDetailsView prefers that passed position (positionFromRoute ?? currentPositionRef.current). This ensures the position used for the update reflects what the user saw when they opened the TP/SL screen, even if the ref changed since. All other call sites (PerpsOrderView, PerpsMarketTabs) were updated to accept the new signature without using the position argument.

The fix achieves its stated goal. Live validation on device confirmed TP/SL can be set from the market details flow with no "No position found" errors.

Full review details

Acceptance Criteria Validation

# Criterion Status Evidence
1 User with open Perps position can navigate to edit TP/SL PASS Recipe step press-auto-close-toggle succeeded
2 TP/SL updated successfully even if position updated via WebSocket before confirm PASS Recipe step press-set-button returned ok:true; no "No position found" in Metro logs
3 No "No position found" error shown after confirming TP/SL PASS watch-logs-after-confirm passed: strings absent from Metro log

Code Quality

  • Pattern adherence: Follows codebase conventions — optional args with _ prefix for ignored params, JSDoc on the modified type, ?? fallback chain.

  • Complexity: Minimal. Single-line logic change (positionFromRoute ?? currentPositionRef.current) at the call site. Signature propagation to three call sites is mechanical.

  • Type safety: The return type of onConfirm was changed from Promise<void> to Promise<{ success: boolean } | void>. This makes the existing return { success: false } in the PerpsMarketDetailsView callback type-correct (it was previously returning an object from a Promise<void> function). All call sites compile cleanly — TSC passes with zero errors.

  • Error handling: Adequate for the stated goal. The { success: false } return is consistent with the pattern used in handleUpdateTPSL.

  • Anti-pattern findings:

    1. PerpsTPSLView.tsx:393 — Silent dismissal on double-null position (suggestion): If both positionFromRoute and currentPositionRef.current are null, onConfirm returns { success: false } but PerpsTPSLView.handleConfirm calls navigation.goBack() unconditionally. Because handleUpdateTPSL is never reached, no error toast is shown. The user sees the TPSL screen close with no feedback. This edge case is extremely unlikely (position was checked before navigation), but worth noting.

    2. PerpsMarketTabs.tsx:333 — No-op onConfirm (pre-existing, not introduced by PR): The PerpsMarketTabs callback remains a complete no-op. When TP/SL is confirmed from the market-tabs position tab, no API call is made. The comment attributes this to WebSocket-based position updates, but TP/SL persistence typically requires an explicit server call. This is pre-existing behavior — the PR only adds the required signature parameters. Not a blocker for this PR.

    3. types/navigation.ts:187 — Non-serializable navigation param (pre-existing): onConfirm is a function passed as a navigation param, which triggers React Navigation's Non-serializable values warning in Metro. This is a known pre-existing pattern in the codebase and not introduced by this PR.

Live Validation

  • Recipe: generated (recipe.json)
  • Result: PASS (15/15 steps)
    • check-positions: ✅ BTC and ETH positions present
    • nav-market-details: ✅ navigated to BTC market details
    • press-auto-close-toggle: ✅ opened TP/SL view
    • press-tp-10pct: ✅ +10% TP calculated and entered
    • watch-logs-before-confirm: ✅ no "No position found"
    • press-set-button: ✅ confirm dispatched
    • watch-logs-after-confirm: ✅ no "No position found"
    • All screenshots captured in evidence/
  • Video: evidence/review.mp4 (2.6 MB, captured full recipe execution)
  • Native changes: none
  • Metro errors: none related to this PR. Pre-existing warnings: react-native-keychain server string, multichain require cycle, ActionModal defaultProps deprecation.
  • Log monitoring: ~90s monitored during recipe execution; no errors introduced by this change.

Correctness

  • Diff vs stated goal: Aligned. The diff does exactly what the PR description says.
  • Edge cases:
    • Position null at confirm (both route param and ref null): handled by return { success: false } — but no user-visible error toast (see anti-pattern finding Inject inpage js #1).
    • Position updated via WebSocket between navigation and confirm: fixed — route param reflects state at navigation time; the fallback to ref covers unusual cases where param wasn't passed.
    • Order flow (no position): _position is ignored — correct, PerpsOrderView only needs TP/SL prices.
    • Market tabs flow: no-op callback unchanged — pre-existing behavior.
  • Race conditions: The original race condition (stale ref between navigation and confirm) is eliminated by using route params. The new approach introduces a different tradeoff: the position at route-param time might differ from the confirm-time state if the position was significantly updated (e.g., partial close). The PR description acknowledges this is acceptable — it matches what the user saw when they intended to set TP/SL.
  • Backward compatibility: The onConfirm signature change is internal to the Perps stack. The PerpsTPSL route type is updated and all three callers updated in the same PR. No external callers.

Static Analysis

  • lint:tsc: PASS — 0 errors (incremental check with .tsbuildinfo)
  • Tests: 84/84 pass (PerpsTPSLView.test.tsx + PerpsMarketDetailsView.test.tsx)

Architecture & Domain

The approach (pass position via nav params rather than relying on a ref) is the correct pattern for React Navigation flows. Refs are for in-component state across re-renders; cross-screen data should flow via navigation params. This PR aligns the pattern with React Navigation best practices.

The signature (position?, takeProfitPrice?, stopLossPrice?, trackingData?) is readable and correctly encodes the "position is optional from the caller's perspective" semantics. The ?? fallback ensures no regression for callers that don't pass position.

Risk Assessment

  • LOW — Targeted fix. All changed code is in the UI layer. No controller, selector, or reducer changes. Type check passes. 84 tests pass. Live recipe validation confirms the fix on device.

Recommended Action

APPROVE

The fix is correct, minimal, and well-tested. The one suggestion worth raising is the silent-dismissal edge case (anti-pattern finding #1) — consider showing an error toast when positionToUse is null before returning { success: false }, so the user isn't left wondering why the screen closed without saving.

Line comments (2 comments: 1 suggestion, 1 nitpick)
  • [suggestion] app/components/UI/Perps/Views/PerpsTPSLView/PerpsTPSLView.tsx:393: Suggestion: navigation.goBack() is called unconditionally after onConfirm. If onConfirm returns { success: false } (which happens when both positionFromRoute and currentPositionRef.current are null), the TPSL screen closes with no user-visible error — handleUpdateTPSL was never reached so its internal toast never fires. The edge case is extremely unlikely in practice (position is verified before navigation), but consider adding a guard:
const result = await onConfirm(position, parseTakeProfitPrice, parseStopLossPrice, trackingData);
if (result && !result.success) {
  // show error toast here instead of navigating back
  return;
}
navigation.goBack();

This makes failures explicit to the user rather than silently closing the sheet.

  • [nitpick] app/components/UI/Perps/components/PerpsMarketTabs/PerpsMarketTabs.tsx:339: Nitpick (pre-existing, not introduced by this PR): The onConfirm callback here is a complete no-op — TP/SL values are discarded. The comment says updates happen via WebSocket, but TP/SL persistence typically requires an explicit API call. If this flow is intentionally disabled, it may be worth tracking the pre-existing gap as a separate ticket to avoid user-facing silent no-ops when accessing TP/SL from the market tabs position view.
Recipe (15 steps defined)
{
  "title": "PR #27409 — TP/SL onConfirm receives position from route params (no stale-ref error)",
  "pre_conditions": [
    "User has at least one open Perps position (BTC)",
    "Metro is running on PR branch fix/perps/tpsl-update-v2",
    "App is authenticated and on Wallet screen"
  ],
  "validate": {
    "runtime": {
      "steps": [
        {
          "id": "check-positions",
          "action": "recipe_ref",
          "ref": "positions",
          "assert": {
            "operator": "not_null",
            "field": "0"
          }
        },
        {
          "id": "nav-market-details",
          "action": "navigate",
          "target": "PerpsMarketDetails",
          "params": {
            "market": {
              "symbol": "BTC",
              "name": "BTC",
              "price": "0",
              "change24h": "0",
              "change24hPercent": "0",
              "volume": "0",
              "maxLeverage": "100"
            },
            "initialTab": "position"
          }
        },
        {
          "id": "wait-market-details-load",
          "action": "wait",
          "ms": 2000
        },
        {
          "id": "screenshot-market-details",
          "action": "screenshot",
          "filename": "evidence/01-market-details.png"
        },
        {
          "id": "press-auto-close-toggle",
          "action": "press",
          "test_id": "position-card-auto-close-toggle"
        },
        {
          "id": "wait-tpsl-view-load",
          "action": "wait",
          "ms": 2000
        },
        {
          "id": "screenshot-tpsl-view",
          "action": "screenshot",
          "filename": "evidence/02-tpsl-view.png"
        },
        {
          "id": "press-tp-10pct",
          "action": "press",
          "test_id": "perps-tpsl-take-profit-percentage-button-10"
        },
        {
          "id": "wait-tp-calculated",
          "action": "wait",
          "ms": 1000
        },
        {
          "id": "screenshot-tpsl-with-tp",
          "action": "screenshot",
          "filename": "evidence/03-tpsl-tp-set.png"
        },
        {
          "id": "watch-logs-before-confirm",
          "action": "log_watch",
          "window_seconds": 5,
          "must_not_appear": ["No position found", "no position found"]
        },
        {
          "id": "press-set-button",
          "action": "press",
          "test_id": "perps-tpsl-bottomsheet"
        },
        {
          "id": "wait-confirm",
          "action": "wait",
          "ms": 3000
        },
        {
          "id": "watch-logs-after-confirm",
          "action": "log_watch",
          "window_seconds": 5,
          "must_not_appear": ["No position found", "no position found"]
        },
        {
          "id": "screenshot-final",
          "action": "screenshot",
          "filename": "evidence/04-after-confirm.png"
        }
      ]
    }
  }
}

review.mp4 (2.6M)

@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Mar 24, 2026
abretonc7s
abretonc7s previously approved these changes Mar 25, 2026

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

Review: PR #27409 — fix(perps): pass position in TP/SL onConfirm

Tier: standard | Tests: all pass | TSC: clean | Device: validated on iOS sim

Sound, minimal fix for the stale-ref race. All three PerpsTPSL callers updated correctly. 2 suggestions (test the primary fix path, check onConfirm result before goBack), 1 nitpick.

Comment thread app/components/UI/Perps/Views/PerpsTPSLView/PerpsTPSLView.tsx
Comment thread app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
@michalconsensys michalconsensys added this pull request to the merge queue Mar 25, 2026
@abretonc7s abretonc7s removed this pull request from the merge queue due to a manual request Mar 25, 2026
…ute test

Add missing _trackingData param to PerpsOrderView's onConfirm callback
to match the full TPSLTrackingData type signature used elsewhere. Fix
PerpsMarketDetailsView test mock (updateTPSL → handleUpdateTPSL) and
add dedicated mock for the direct usePerpsTPSLUpdate import path. Add
test verifying handleUpdateTPSL is called with positionFromRoute when
currentPositionRef is stale.
@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Mar 25, 2026
@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 changes are focused on the Perps TP/SL (Take Profit/Stop Loss) flow within the Perps feature:

  1. navigation.ts: The onConfirm callback signature in PerpsNavigationParamList was updated to add a new first parameter position?: Position and change the return type to Promise<{ success: boolean } | void>. This is a breaking interface change that affects all callers.

  2. PerpsTPSLView.tsx: Now passes position from route params as the first argument to onConfirm to fix a race condition where the parent's position ref could be stale.

  3. PerpsMarketDetailsView.tsx: Updated to use positionFromRoute ?? currentPositionRef.current - prefers the position passed from TPSL view over the potentially stale ref.

  4. PerpsOrderView.tsx and PerpsMarketTabs.tsx: Updated to accept the new _position parameter (ignored in these flows).

  5. Test files: Unit tests updated to match new callback signatures.

Tag selection rationale:

  • SmokePerps: Primary tag - these changes directly affect the Perps TP/SL flow (editing existing positions, setting take profit/stop loss). The bug fix changes how position data flows through the confirmation callback, which is a core Perps interaction.
  • SmokeWalletPlatform: Required per SmokePerps tag description - "When selecting SmokePerps, also select SmokeWalletPlatform (Trending section)". Perps is also a section inside the Trending tab.
  • SmokeConfirmations: Required per SmokePerps tag description - "When selecting SmokePerps, also select SmokeConfirmations (Add Funds deposits are on-chain transactions)". The TP/SL update flow involves on-chain transactions.

The risk is medium because while the changes are contained to Perps, the callback signature change touches multiple components and fixes a race condition that could affect position management reliability.

Performance Test Selection:
The changes touch the Perps TP/SL confirmation flow and position management. While the changes are primarily bug fixes (race condition fix for stale position ref), the callback signature change and position data flow modifications could potentially affect the performance of the Perps add funds and position management flows. Running @PerformancePreps is appropriate to ensure no performance regression was introduced in the Perps trading interface.

View GitHub Actions results

@github-actions

Copy link
Copy Markdown
Contributor

E2E Fixture Validation — Schema is up to date
16 value mismatches detected (expected — fixture represents an existing user).
View details

@sonarqubecloud

Copy link
Copy Markdown

@abretonc7s abretonc7s added this pull request to the merge queue Mar 30, 2026
Merged via the queue into main with commit e4c7d07 Mar 30, 2026
104 checks passed
@abretonc7s abretonc7s deleted the fix/perps/tpsl-update-v2 branch March 30, 2026 11:42
@github-actions github-actions Bot locked and limited conversation to collaborators Mar 30, 2026
@metamaskbot metamaskbot added the release-7.73.0 Issue or pull request that will be included in release 7.73.0 label Mar 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.73.0 Issue or pull request that will be included in release 7.73.0 risk-medium Moderate testing recommended · Possible bug introduction risk size-M team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants