Skip to content

Commit 2b39326

Browse files
chore(runway): cherry-pick fix: cp-7.58.0 close position disabled when receiveAmount <0 (#21643)
- fix: cp-7.58.0 close position disabled when receiveAmount <0 (#21622) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** ### Problem Users were blocked from closing positions when fees exceeded recoverable value (receiveAmount <= 0), creating a financial trap where they couldn't exit losing positions. This particularly affected accounts that lost all available margin in the position after price movement. ### Solution Changed negative receive amount validation from a blocking error to a non-blocking warning, allowing users to always close positions while still informing them of the cost. ### Changes usePerpsClosePositionValidation.ts: Moved receiveAmount < 0 check from errors to warnings array PerpsClosePositionView.tsx: Removed receiveAmount <= 0 from button disable logic en.json: Added negative_receive_warning locale string usePerpsClosePositionValidation.test.ts: Updated test to expect warning instead of error ### Impact ✅ Users can now always exit positions (critical for risk management) ✅ Clear warning can be shown when closing will cost money (we are not currently showing warnings) <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **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: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-1887 Fixes: #21624 ## **Manual testing steps** *** You may need to mock receiveAmount to simulate less than 0 on main and then compare with this branch ```gherkin Feature: close position disabled when receiveAmount <0 Scenario: user creates a btc position at the min amount Given user loses all of the margin in their position to the point of have nothing left to get back When user attempts to close their position at total loss <0 Then user should be able to close that position ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** [Message from Matthieu Saint Olive in #tmp-beta-bip44-mobile](https://consensys.slack.com/archives/C09H8PG9FBJ/p1760716074716439) <!-- [screenshots/recordings] --> ### **After** n/a position can close <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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] > Converts negative receive amount from blocking error to non-blocking warning so users can close positions; updates validation, button disable logic, tests, and locale strings. > > - **Perps Close Position UI**: > - Remove `receiveAmount <= 0` from confirm button `isDisabled` logic in `PerpsClosePositionView.tsx`. > - **Validation Hook** (`usePerpsClosePositionValidation`): > - Change negative `receiveAmount` check from error to warning (`negative_receive_warning` with formatted amount). > - Update validation docs and numbering; keep other validations unchanged. > - **Tests**: > - Adjust test to expect a warning (and still valid) for negative `receiveAmount` in `usePerpsClosePositionValidation.test.ts`. > - **Localization**: > - Add `perps.close_position.negative_receive_warning` string in `en.json`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 727e0de. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [cfb4d79](cfb4d79) Co-authored-by: Nicholas Smith <[email protected]>
1 parent 468f35c commit 2b39326

File tree

4 files changed

+22
-15
lines changed

4 files changed

+22
-15
lines changed

app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,6 @@ const PerpsClosePositionView: React.FC = () => {
800800
(orderType === 'limit' &&
801801
(!limitPrice || parseFloat(limitPrice) <= 0)) ||
802802
(orderType === 'market' && closePercentage === 0) ||
803-
receiveAmount <= 0 ||
804803
!validationResult.isValid
805804
}
806805
loading={isClosing}

app/components/UI/Perps/hooks/usePerpsClosePositionValidation.test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('usePerpsClosePositionValidation', () => {
110110
expect(result.current.errors).toHaveLength(0);
111111
});
112112

113-
it('should return error when user will receive negative amount', async () => {
113+
it('returns warning (not error) when user will receive negative amount', async () => {
114114
mockValidateClosePosition.mockResolvedValue({ isValid: true });
115115

116116
const params = {
@@ -126,9 +126,12 @@ describe('usePerpsClosePositionValidation', () => {
126126
expect(result.current.isValidating).toBe(false);
127127
});
128128

129-
expect(result.current.isValid).toBe(false);
130-
expect(result.current.errors).toContain(
131-
strings('perps.close_position.negative_receive_amount'),
129+
expect(result.current.isValid).toBe(true);
130+
expect(result.current.errors).toHaveLength(0);
131+
expect(result.current.warnings).toContain(
132+
strings('perps.close_position.negative_receive_warning', {
133+
amount: '100.00',
134+
}),
132135
);
133136
});
134137

app/components/UI/Perps/hooks/usePerpsClosePositionValidation.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,12 @@ interface ValidationResult {
5454
* - Example: ERROR - Trying to close a $5 position (below $6 minimum on mainnet)
5555
* - Note: This shouldn't happen if positions were opened correctly
5656
*
57-
* 4. NEGATIVE RECEIVE AMOUNT
58-
* - When fees exceed the position value + P&L
59-
* - Example: ERROR - Position worth $10, fees are $12, user would receive -$2
60-
* - Protects users from paying to close losing positions
61-
*
62-
* 5. MISSING LIMIT PRICE (checked by protocol validation)
57+
* 4. MISSING LIMIT PRICE (checked by protocol validation)
6358
* - Limit orders require a price to be specified
6459
* - Example: ERROR - Limit order with no price or price <= 0
6560
* - Note: This is validated by the provider, not duplicate-checked in UI
6661
*
67-
* 6. ZERO AMOUNT FOR MARKET ORDER
62+
* 5. ZERO AMOUNT FOR MARKET ORDER
6863
* - Market orders with 0% close percentage
6964
* - Example: ERROR - Slider at 0% for market order
7065
*
@@ -84,6 +79,11 @@ interface ValidationResult {
8479
* - Example: WARNING - Closing only 2% of a $5,000 position ($100)
8580
* - Useful for taking small profits but may not justify transaction costs
8681
*
82+
* 3. NEGATIVE RECEIVE AMOUNT
83+
* - When fees exceed the recoverable value from the position
84+
* - Example: WARNING - Closing will cost you $2 due to fees and losses
85+
* - User can still proceed to close the position if they choose to exit
86+
*
8787
* ==========================================
8888
* SPECIAL BEHAVIORS:
8989
* ==========================================
@@ -168,9 +168,13 @@ export function usePerpsClosePositionValidation(
168168
);
169169
}
170170

171-
// Check if user will receive a positive amount after fees
172-
if (receiveAmount <= 0) {
173-
errors.push(strings('perps.close_position.negative_receive_amount'));
171+
// Warn if user will receive negative amount (but allow them to proceed)
172+
if (receiveAmount < 0) {
173+
warnings.push(
174+
strings('perps.close_position.negative_receive_warning', {
175+
amount: Math.abs(receiveAmount).toFixed(2),
176+
}),
177+
);
174178
}
175179

176180
// Limit order specific validation (price warning only - required check is done by protocol)

locales/languages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,7 @@
11961196
"minimum_remaining_error": "Cannot partial close: remaining position (${{remaining}}) would be below minimum order size (${{minimum}}). Please close 100% instead.",
11971197
"must_close_full_below_minimum": "Position value is below $10. You must close 100% of the position.",
11981198
"negative_receive_amount": "The fees exceed your position value",
1199+
"negative_receive_warning": "Closing this position will cost you ${{amount}} due to fees and losses.",
11991200
"no_amount_selected": "Please select an amount to close",
12001201
"order_type_reverted_to_market_order": "Changed to market order",
12011202
"you_need_set_price_limit_order": "You need to set a price for a limit order."

0 commit comments

Comments
 (0)