Skip to content

Commit f0e76d0

Browse files
michalconsensysnickewansmithdylanbutler1
authored
fix(perps): change fee rounding for small fees (#22264)
<!-- 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** Adds a small-fee threshold to show "< $0.01" for fees below $0.01, improving readability and avoiding misleading precision. ## **Changelog** CHANGELOG entry: Display < $0.01 for small fees in Perps ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-1970 ## **Manual testing steps** ```gherkin Feature: Perps position transaction view fee display Scenario: user views transaction with small fee Given a user has a closed position transaction with a fee less than $0.01 (e.g., $0.005) When user views the transaction details screen Then the fee should display as "< $0.01" instead of the exact amount ``` ## **Screenshots/Recordings** ### **Before** ![IMG_EB5F710DB10B-1](https://github.com/user-attachments/assets/eff4f430-b913-4f28-8ec1-f703a7cf9115) <!-- [screenshots/recordings] --> ### **After** <img width="1170" height="2532" alt="Simulator Screenshot - iPhone 16e - 2025-11-07 at 11 04 09" src="https://github.com/user-attachments/assets/6f3a891f-b203-41f0-a241-76b0c2979635" /> ## **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] > Introduces `formatFee` to show "< $0.01" for fees below $0.01 and updates Perps transaction views and tests to use it. > > - **Utilities**: > - Add `formatFee` in `app/components/UI/Perps/utils/formatUtils.ts` to return `"$0"`, `"< $0.01"`, or formatted fiat; add comprehensive unit tests. > - **Views**: > - Replace ad-hoc fee formatting with `formatFee` in `PerpsOrderTransactionView.tsx` and `PerpsPositionTransactionView.tsx`; ensure unfilled orders show `$0`. > - **Tests**: > - Update and expand tests in transaction view specs to assert `"< $0.01"` behavior and edge cases; keep existing explorer navigation tests intact. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cf07474. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Nicholas Smith <nick.smith@consensys.net> Co-authored-by: dylanbutler1 <99672693+dylanbutler1@users.noreply.github.com>
1 parent 047a418 commit f0e76d0

File tree

6 files changed

+296
-40
lines changed

6 files changed

+296
-40
lines changed

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.test.tsx

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe('PerpsOrderTransactionView', () => {
9494
mockUsePerpsOrderFees.mockReturnValue({
9595
totalFee: 10.5,
9696
protocolFee: 7.5,
97-
metamaskFee: 3.0,
97+
metamaskFee: 3,
9898
protocolFeeRate: 0.1,
9999
metamaskFeeRate: 0.05,
100100
isLoadingMetamaskFee: false,
@@ -162,7 +162,7 @@ describe('PerpsOrderTransactionView', () => {
162162
expect(zeroFees).toHaveLength(3); // All three fees should be $0
163163
});
164164

165-
it('should handle small fees correctly', () => {
165+
it('should show "< $0.01" for fees less than 0.01', () => {
166166
mockUsePerpsOrderFees.mockReturnValue({
167167
totalFee: 0.005,
168168
protocolFee: 0.003,
@@ -173,11 +173,135 @@ describe('PerpsOrderTransactionView', () => {
173173
error: null,
174174
});
175175

176-
const { getByText } = render(<PerpsOrderTransactionView />);
176+
const { getAllByText } = render(<PerpsOrderTransactionView />);
177+
178+
// All three fees should show "< $0.01" since they're all less than 0.01
179+
const smallFeeLabels = getAllByText('< $0.01');
180+
expect(smallFeeLabels).toHaveLength(3);
181+
});
182+
183+
it('should format fees normally when they are exactly 0.01', () => {
184+
mockUsePerpsOrderFees.mockReturnValue({
185+
totalFee: 0.03,
186+
protocolFee: 0.01,
187+
metamaskFee: 0.01,
188+
protocolFeeRate: 0.1,
189+
metamaskFeeRate: 0.05,
190+
isLoadingMetamaskFee: false,
191+
error: null,
192+
});
193+
194+
const { getAllByText, queryByText, getByText } = render(
195+
<PerpsOrderTransactionView />,
196+
);
197+
198+
// Fees at exactly 0.01 should be formatted normally, not show "< $0.01"
199+
expect(queryByText('< $0.01')).toBeNull();
200+
// Both metamask and protocol fees are 0.01
201+
const fee01Labels = getAllByText('$0.01');
202+
expect(fee01Labels.length).toBeGreaterThanOrEqual(2);
203+
expect(getByText('$0.03')).toBeTruthy(); // Total fee
204+
});
205+
206+
it('should format fees normally when they are greater than 0.01', () => {
207+
mockUsePerpsOrderFees.mockReturnValue({
208+
totalFee: 0.015,
209+
protocolFee: 0.012,
210+
metamaskFee: 0.003,
211+
protocolFeeRate: 0.1,
212+
metamaskFeeRate: 0.05,
213+
isLoadingMetamaskFee: false,
214+
error: null,
215+
});
216+
217+
const { getByText, getAllByText } = render(<PerpsOrderTransactionView />);
218+
219+
// Metamask fee is less than 0.01, should show "< $0.01"
220+
expect(getAllByText('< $0.01')).toHaveLength(1);
221+
// Protocol and total fees are >= 0.01, should be formatted normally
222+
expect(getByText('$0.01')).toBeTruthy(); // Protocol fee formatted
223+
expect(getByText('$0.02')).toBeTruthy(); // Total fee formatted (rounded)
224+
});
225+
226+
it('should handle mixed small and large fees correctly', () => {
227+
mockUsePerpsOrderFees.mockReturnValue({
228+
totalFee: 0.025,
229+
protocolFee: 0.02,
230+
metamaskFee: 0.005,
231+
protocolFeeRate: 0.1,
232+
metamaskFeeRate: 0.05,
233+
isLoadingMetamaskFee: false,
234+
error: null,
235+
});
236+
237+
const { getByText, getAllByText } = render(<PerpsOrderTransactionView />);
238+
239+
// Metamask fee is less than 0.01
240+
const smallFeeLabels = getAllByText('< $0.01');
241+
expect(smallFeeLabels).toHaveLength(1);
242+
// Protocol and total fees are >= 0.01, should be formatted
243+
expect(getByText('$0.02')).toBeTruthy(); // Protocol fee
244+
expect(getByText('$0.03')).toBeTruthy(); // Total fee (rounded)
245+
});
246+
247+
it('should handle edge case: fee just below 0.01 threshold', () => {
248+
mockUsePerpsOrderFees.mockReturnValue({
249+
totalFee: 0.029,
250+
protocolFee: 0.0099,
251+
metamaskFee: 0.0099,
252+
protocolFeeRate: 0.1,
253+
metamaskFeeRate: 0.05,
254+
isLoadingMetamaskFee: false,
255+
error: null,
256+
});
257+
258+
const { getAllByText } = render(<PerpsOrderTransactionView />);
259+
260+
// Both metamask and protocol fees are just below 0.01
261+
const smallFeeLabels = getAllByText('< $0.01');
262+
expect(smallFeeLabels).toHaveLength(2);
263+
// Total fee is >= 0.01, should be formatted
264+
});
265+
266+
it('should handle edge case: fee just above 0.01 threshold', () => {
267+
mockUsePerpsOrderFees.mockReturnValue({
268+
totalFee: 0.0201,
269+
protocolFee: 0.0101,
270+
metamaskFee: 0.01,
271+
protocolFeeRate: 0.1,
272+
metamaskFeeRate: 0.05,
273+
isLoadingMetamaskFee: false,
274+
error: null,
275+
});
276+
277+
const { queryByText, getAllByText, getByText } = render(
278+
<PerpsOrderTransactionView />,
279+
);
280+
281+
// All fees are >= 0.01, should be formatted normally
282+
expect(queryByText('< $0.01')).toBeNull();
283+
// Metamask fee and protocol fee (rounded) both show $0.01
284+
const fee01Labels = getAllByText('$0.01');
285+
expect(fee01Labels.length).toBeGreaterThanOrEqual(2);
286+
expect(getByText('$0.02')).toBeTruthy(); // Total fee (rounded)
287+
});
288+
289+
it('should show "< $0.01" for all fees when all are below threshold', () => {
290+
mockUsePerpsOrderFees.mockReturnValue({
291+
totalFee: 0.008,
292+
protocolFee: 0.005,
293+
metamaskFee: 0.003,
294+
protocolFeeRate: 0.1,
295+
metamaskFeeRate: 0.05,
296+
isLoadingMetamaskFee: false,
297+
error: null,
298+
});
299+
300+
const { getAllByText } = render(<PerpsOrderTransactionView />);
177301

178-
expect(getByText('$0.002')).toBeTruthy();
179-
expect(getByText('$0.003')).toBeTruthy();
180-
expect(getByText('$0.005')).toBeTruthy();
302+
// All three fees are below 0.01
303+
const smallFeeLabels = getAllByText('< $0.01');
304+
expect(smallFeeLabels).toHaveLength(3);
181305
});
182306

183307
it('should navigate to block explorer in browser tab when button is pressed', () => {

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.tsx

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import Text, {
1111
TextVariant,
1212
} from '../../../../../component-library/components/Texts/Text';
1313

14-
import { BigNumber } from 'bignumber.js';
1514
import { useSelector } from 'react-redux';
1615
import { PerpsTransactionSelectorsIDs } from '../../../../../../e2e/selectors/Perps/Perps.selectors';
1716
import Button, {
@@ -28,6 +27,7 @@ import { usePerpsBlockExplorerUrl, usePerpsOrderFees } from '../../hooks';
2827
import { PerpsNavigationParamList } from '../../types/navigation';
2928
import { PerpsOrderTransactionRouteProp } from '../../types/transactionHistory';
3029
import {
30+
formatFee,
3131
formatPerpsFiat,
3232
formatTransactionDate,
3333
} from '../../utils/formatUtils';
@@ -103,44 +103,21 @@ const PerpsOrderTransactionView: React.FC = () => {
103103
];
104104

105105
const isFilled = transaction.order?.text === 'Filled';
106+
106107
// Fee breakdown
107108

108109
const feeRows = [
109110
{
110111
label: strings('perps.transactions.order.metamask_fee'),
111-
value: `${
112-
isFilled
113-
? `${
114-
BigNumber(metamaskFee).isLessThan(0.01)
115-
? `$${metamaskFee}`
116-
: formatPerpsFiat(metamaskFee)
117-
}`
118-
: '$0'
119-
}`,
112+
value: formatFee(isFilled ? metamaskFee : 0),
120113
},
121114
{
122115
label: strings('perps.transactions.order.hyperliquid_fee'),
123-
value: `${
124-
isFilled
125-
? `${
126-
BigNumber(protocolFee).isLessThan(0.01)
127-
? `$${protocolFee}`
128-
: formatPerpsFiat(protocolFee)
129-
}`
130-
: '$0'
131-
}`,
116+
value: formatFee(isFilled ? protocolFee : 0),
132117
},
133118
{
134119
label: strings('perps.transactions.order.total_fee'),
135-
value: `${
136-
isFilled
137-
? `${
138-
BigNumber(totalFee).isLessThan(0.01)
139-
? `$${totalFee}`
140-
: formatPerpsFiat(totalFee)
141-
}`
142-
: '$0'
143-
}`,
120+
value: formatFee(isFilled ? totalFee : 0),
144121
},
145122
];
146123

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsPositionTransactionView.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ describe('PerpsPositionTransactionView', () => {
361361
expect(getByText('$5')).toBeOnTheScreen();
362362
});
363363

364-
it('should display fees with $ prefix directly for amounts < 0.01', () => {
364+
it('should display fees with < $0.01 label for amounts < 0.01', () => {
365365
// Given a transaction with fee less than 0.01
366366
const smallFeeTransaction = {
367367
...mockTransaction,
@@ -379,9 +379,9 @@ describe('PerpsPositionTransactionView', () => {
379379
state: mockInitialState,
380380
});
381381

382-
// Then fee should display with $ prefix directly (not formatted through formatPerpsFiat)
382+
// Then fee should display with < $0.01 label (not the actual value)
383383
expect(getByText('Total fees')).toBeOnTheScreen();
384-
expect(getByText('$0.005')).toBeOnTheScreen();
384+
expect(getByText('< $0.01')).toBeOnTheScreen();
385385
});
386386

387387
it('should not render points when not present', () => {

app/components/UI/Perps/Views/PerpsTransactionsView/PerpsPositionTransactionView.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
PerpsTransaction,
3131
} from '../../types/transactionHistory';
3232
import {
33+
formatFee,
3334
formatPerpsFiat,
3435
formatTransactionDate,
3536
PRICE_RANGES_UNIVERSAL,
@@ -115,9 +116,7 @@ const PerpsPositionTransactionView: React.FC = () => {
115116
transaction.fill?.fee !== undefined &&
116117
transaction.fill?.fee !== null && {
117118
label: strings('perps.transactions.position.fees'),
118-
value: BigNumber(transaction.fill.fee).isGreaterThan(0.01)
119-
? formatPerpsFiat(transaction.fill.fee)
120-
: `$${transaction.fill.fee}`,
119+
value: formatFee(transaction.fill.fee),
121120
textColor: TextColor.Default,
122121
},
123122
].filter(Boolean);

0 commit comments

Comments
 (0)