Skip to content

Commit d47acc1

Browse files
fix: cp-7.60.0 target network fee alert in metamask pay (#22839)
## **Description** Ensure the insufficient balance alert is displayed for Predict confirmations when the token is Polygon USDC.e. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: #22713 ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** ## **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] > Refines insufficient balance alert logic to consider pay token vs required tokens and updates result readiness to check required token source amounts; adds corresponding tests. > > - **Alerts (useInsufficientBalanceAlert)**: > - Consider `payToken` and `useTransactionPayRequiredTokens()`; compute primary required token and show alert only when relevant. > - Early-exit when a `payToken` is set for a different token; only ignore `TransactionType.predictWithdraw`. > - Update memo deps accordingly. > - **Custom Amount UI**: > - `useIsResultReady` now checks for source amounts matching non-skippable required tokens instead of `sourceAmounts` length. > - **Tests**: > - Add/adjust unit tests for pay-token/required-token scenarios and ignored types. > - Update mocks, types (`Hex`), and defaults for new hooks. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6fe6488. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent f08fdf5 commit d47acc1

File tree

3 files changed

+89
-9
lines changed

3 files changed

+89
-9
lines changed

app/components/Views/confirmations/components/info/custom-amount-info/custom-amount-info.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,20 @@ function useIsResultReady({
250250
}) {
251251
const quotes = useTransactionPayQuotes();
252252
const isQuotesLoading = useIsTransactionPayLoading();
253+
const requiredTokens = useTransactionPayRequiredTokens();
253254
const sourceAmounts = useTransactionPaySourceAmounts();
254255

256+
const hasSourceAmount = sourceAmounts?.some((a) =>
257+
requiredTokens.some(
258+
(rt) =>
259+
rt.address.toLowerCase() === a.targetTokenAddress.toLowerCase() &&
260+
!rt.skipIfBalance,
261+
),
262+
);
263+
255264
return (
256265
!isKeyboardVisible &&
257-
(isQuotesLoading || Boolean(quotes?.length) || !sourceAmounts?.length)
266+
(isQuotesLoading || Boolean(quotes?.length) || !hasSourceAmount)
258267
);
259268
}
260269

app/components/Views/confirmations/hooks/alerts/useInsufficientBalanceAlert.test.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import { noop } from 'lodash';
1717
import { useConfirmationContext } from '../../context/confirmation-context';
1818
import { useRampNavigation } from '../../../../UI/Ramp/hooks/useRampNavigation';
1919
import { useIsGaslessSupported } from '../gas/useIsGaslessSupported';
20+
import { useTransactionPayRequiredTokens } from '../pay/useTransactionPayData';
21+
import {
22+
TransactionPayRequiredToken,
23+
TransactionPaymentToken,
24+
} from '@metamask/transaction-pay-controller';
25+
import { Hex } from '@metamask/utils';
2026

2127
jest.mock('../../../../../util/navigation/navUtils', () => ({
2228
useParams: jest.fn().mockReturnValue({
@@ -30,6 +36,7 @@ jest.mock('react-redux', () => ({
3036
...jest.requireActual('react-redux'),
3137
useSelector: jest.fn().mockImplementation((selector) => selector()),
3238
}));
39+
3340
jest.mock('@react-navigation/native', () => {
3441
const actualNav = jest.requireActual('@react-navigation/native');
3542
return {
@@ -39,6 +46,7 @@ jest.mock('@react-navigation/native', () => {
3946
}),
4047
};
4148
});
49+
4250
jest.mock('../useConfirmActions');
4351
jest.mock('../transactions/useTransactionMetadataRequest');
4452
jest.mock('../pay/useTransactionPayToken');
@@ -54,6 +62,8 @@ jest.mock('../../../../UI/Ramp/hooks/useRampNavigation', () => ({
5462
RampMode: { AGGREGATOR: 'AGGREGATOR', DEPOSIT: 'DEPOSIT' },
5563
}));
5664
jest.mock('../gas/useIsGaslessSupported');
65+
jest.mock('../pay/useTransactionPayData');
66+
jest.mock('../pay/useTransactionPayData');
5767

5868
describe('useInsufficientBalanceAlert', () => {
5969
const mockUseTransactionMetadataRequest = jest.mocked(
@@ -69,8 +79,12 @@ describe('useInsufficientBalanceAlert', () => {
6979
const mockUseRampNavigation = jest.mocked(useRampNavigation);
7080
const mockGoToRamps = jest.fn();
7181
const useIsGaslessSupportedMock = jest.mocked(useIsGaslessSupported);
82+
const useTransactionPayRequiredTokensMock = jest.mocked(
83+
useTransactionPayRequiredTokens,
84+
);
85+
const useTransactionPayTokenMock = jest.mocked(useTransactionPayToken);
7286

73-
const mockChainId = '0x1';
87+
const mockChainId = '0x1' as Hex;
7488
const mockFromAddress = '0x123';
7589
const mockNativeCurrency = 'ETH';
7690
const mockTransaction = {
@@ -126,6 +140,13 @@ describe('useInsufficientBalanceAlert', () => {
126140
mockUseRampNavigation.mockReturnValue({
127141
goToRamps: mockGoToRamps,
128142
});
143+
144+
useTransactionPayRequiredTokensMock.mockReturnValue([]);
145+
146+
useTransactionPayTokenMock.mockReturnValue({
147+
payToken: undefined,
148+
setPayToken: jest.fn(),
149+
});
129150
});
130151

131152
it('return empty array when no transaction metadata is available', () => {
@@ -229,14 +250,48 @@ describe('useInsufficientBalanceAlert', () => {
229250
it('returns empty array if transaction type ignored', () => {
230251
mockUseTransactionMetadataRequest.mockReturnValue({
231252
...mockTransaction,
232-
type: TransactionType.perpsDeposit,
253+
type: TransactionType.predictWithdraw,
233254
} as unknown as TransactionMeta);
234255

235256
const { result } = renderHook(() => useInsufficientBalanceAlert());
236257

237258
expect(result.current).toStrictEqual([]);
238259
});
239260

261+
it('returns no alert if pay token', () => {
262+
useTransactionPayTokenMock.mockReturnValue({
263+
payToken: {
264+
address: '0x123' as Hex,
265+
} as TransactionPaymentToken,
266+
setPayToken: jest.fn(),
267+
});
268+
269+
const { result } = renderHook(() => useInsufficientBalanceAlert());
270+
271+
expect(result.current).toStrictEqual([]);
272+
});
273+
274+
it('returns alert if pay token matches required token', () => {
275+
useTransactionPayTokenMock.mockReturnValue({
276+
payToken: {
277+
address: '0x123' as Hex,
278+
chainId: mockChainId,
279+
} as TransactionPaymentToken,
280+
setPayToken: jest.fn(),
281+
});
282+
283+
useTransactionPayRequiredTokensMock.mockReturnValue([
284+
{
285+
address: '0x123' as Hex,
286+
chainId: mockChainId,
287+
} as TransactionPayRequiredToken,
288+
]);
289+
290+
const { result } = renderHook(() => useInsufficientBalanceAlert());
291+
292+
expect(result.current).toHaveLength(1);
293+
});
294+
240295
describe('when ignoreGasFeeToken is true', () => {
241296
it('returns empty array', () => {
242297
mockUseAccountNativeBalance.mockReturnValue({

app/components/Views/confirmations/hooks/alerts/useInsufficientBalanceAlert.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ import { useConfirmationContext } from '../../context/confirmation-context';
2424
import { useIsGaslessSupported } from '../gas/useIsGaslessSupported';
2525
import { TransactionType } from '@metamask/transaction-controller';
2626
import { hasTransactionType } from '../../utils/transaction';
27+
import { useTransactionPayToken } from '../pay/useTransactionPayToken';
28+
import { useTransactionPayRequiredTokens } from '../pay/useTransactionPayData';
2729

28-
const IGNORE_TYPES = [
29-
TransactionType.perpsDeposit,
30-
TransactionType.predictDeposit,
31-
TransactionType.predictWithdraw,
32-
];
30+
const IGNORE_TYPES = [TransactionType.predictWithdraw];
3331

3432
const HEX_ZERO = '0x0';
3533

@@ -48,9 +46,25 @@ export const useInsufficientBalanceAlert = ({
4846
const { isTransactionValueUpdating } = useConfirmationContext();
4947
const { onReject } = useConfirmActions();
5048
const { isSupported: isGaslessSupported } = useIsGaslessSupported();
49+
const { payToken } = useTransactionPayToken();
50+
const requiredTokens = useTransactionPayRequiredTokens();
51+
52+
const primaryRequiredToken = (requiredTokens ?? []).find(
53+
(token) => !token.skipIfBalance,
54+
);
55+
56+
const isPayTokenTarget =
57+
payToken &&
58+
payToken.chainId === primaryRequiredToken?.chainId &&
59+
payToken.address.toLowerCase() ===
60+
primaryRequiredToken?.address.toLowerCase();
5161

5262
return useMemo(() => {
53-
if (!transactionMetadata || isTransactionValueUpdating) {
63+
if (
64+
!transactionMetadata ||
65+
isTransactionValueUpdating ||
66+
(payToken && !isPayTokenTarget)
67+
) {
5468
return [];
5569
}
5670

@@ -117,9 +131,11 @@ export const useInsufficientBalanceAlert = ({
117131
balanceWeiInHex,
118132
ignoreGasFeeToken,
119133
isGaslessSupported,
134+
isPayTokenTarget,
120135
isTransactionValueUpdating,
121136
networkConfigurations,
122137
onReject,
138+
payToken,
123139
transactionMetadata,
124140
goToRamps,
125141
]);

0 commit comments

Comments
 (0)