Skip to content

Commit

Permalink
fix: Fix send flow max value issue (#29960)
Browse files Browse the repository at this point in the history
<!--
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**

<!--
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?
-->

This PR aims to fix send max value issue for redesigned confirmations.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29960?quickstart=1)

## **Related issues**

Fixes: #29903

## **Manual testing steps**

1. Initiate a native send flow from wallet
2. Click max in the amount picker
3. Go to next step (confirmation)
4. See that value changes when gas is changed

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->



https://github.com/user-attachments/assets/14a85e29-b7d5-4bfe-b013-25f1954103cc



### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/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-extension/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.

---------

Co-authored-by: Harika <[email protected]>
  • Loading branch information
OGPoyraz and hjetpoluru authored Jan 29, 2025
1 parent c00289d commit 180641b
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 3 deletions.
3 changes: 2 additions & 1 deletion test/data/mock-state.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"gas": "0x153e2",
"value": "0x0"
}
}
},
"maxValueMode": {}
},
"history": {
"mostRecentOverviewPage": "/mostRecentOverviewPage"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('useFeeCalculations', () => {
"maxFeeFiat": "< $0.01",
"maxFeeFiatWith18SignificantDigits": "0",
"maxFeeNative": "0 ETH",
"preciseNativeFeeInHex": "0x0",
}
`);
});
Expand Down Expand Up @@ -61,6 +62,7 @@ describe('useFeeCalculations', () => {
"maxFeeFiat": "$0.07",
"maxFeeFiatWith18SignificantDigits": null,
"maxFeeNative": "0.0001 ETH",
"preciseNativeFeeInHex": "0x3be226d2d900",
}
`);
});
Expand Down Expand Up @@ -92,6 +94,7 @@ describe('useFeeCalculations', () => {
"maxFeeFiat": "$0.07",
"maxFeeFiatWith18SignificantDigits": null,
"maxFeeNative": "0.0001 ETH",
"preciseNativeFeeInHex": "0x364ba3e2d900",
}
`);
});
Expand Down Expand Up @@ -122,6 +125,7 @@ describe('useFeeCalculations', () => {
"maxFeeFiat": "$0.07",
"maxFeeFiatWith18SignificantDigits": null,
"maxFeeNative": "0.0001 ETH",
"preciseNativeFeeInHex": "0x103be226d2d900",
}
`);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GasFeeEstimates } from '@metamask/gas-fee-controller';
import { TransactionMeta } from '@metamask/transaction-controller';
import { Hex } from '@metamask/utils';
import { add0x, Hex } from '@metamask/utils';
import { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { EtherDenomination } from '../../../../../../../shared/constants/common';
Expand Down Expand Up @@ -80,6 +80,7 @@ export function useFeeCalculations(transactionMeta: TransactionMeta) {
currentCurrencyFee,
currentCurrencyFeeWith18SignificantDigits,
nativeCurrencyFee,
preciseNativeFeeInHex: add0x(hexFee),
};
},
[conversionRate, currentCurrency, fiatFormatter],
Expand Down Expand Up @@ -188,5 +189,6 @@ export function useFeeCalculations(transactionMeta: TransactionMeta) {
maxFeeFiat,
maxFeeFiatWith18SignificantDigits,
maxFeeNative,
preciseNativeFeeInHex: estimatedFees.preciseNativeFeeInHex,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { renderHook } from '@testing-library/react-hooks';
import { useDispatch, useSelector } from 'react-redux';
import { TransactionType } from '@metamask/transaction-controller';
import { updateEditableParams } from '../../../../../../store/actions';
import { useConfirmContext } from '../../../../context/confirm';
import { useFeeCalculations } from './useFeeCalculations';
import { useMaxValueRefresher } from './useMaxValueRefresher';

jest.mock('react-redux', () => ({
useDispatch: jest.fn(),
useSelector: jest.fn(),
}));

jest.mock('../../../../../../store/actions', () => ({
updateEditableParams: jest.fn(),
}));

jest.mock('../../../../context/confirm', () => ({
useConfirmContext: jest.fn(),
}));

jest.mock('../hooks/useFeeCalculations', () => ({
useFeeCalculations: jest.fn(),
}));

describe('useMaxValueRefresher', () => {
const dispatchMock = jest.fn();
const simpleSendTransactionMetaMock = {
id: '1',
type: TransactionType.simpleSend,
};

beforeEach(() => {
(useDispatch as jest.Mock).mockReturnValue(dispatchMock);
(useConfirmContext as jest.Mock).mockReturnValue({
currentConfirmation: simpleSendTransactionMetaMock,
});
jest.clearAllMocks();
});

it('updates transaction value in max amount mode for simpleSend', () => {
const balance = '0x111';
const preciseNativeFeeInHex = '0x001';
const newValue = '0x110';

(useSelector as jest.Mock)
.mockReturnValueOnce(balance)
.mockReturnValueOnce(true);

(useFeeCalculations as jest.Mock).mockReturnValue({
preciseNativeFeeInHex,
});

renderHook(() => useMaxValueRefresher());

expect(dispatchMock).toHaveBeenCalledWith(
updateEditableParams(simpleSendTransactionMetaMock.id, {
value: newValue,
}),
);
});

it('does not update transaction value if not in max amount mode', () => {
(useSelector as jest.Mock)
.mockReturnValueOnce('0x111')
.mockReturnValueOnce(false);

renderHook(() => useMaxValueRefresher());

expect(dispatchMock).not.toHaveBeenCalled();
});

it('does not update transaction value if transaction type is not simpleSend', () => {
const transactionMeta = { id: '1', type: 'otherType' };
(useConfirmContext as jest.Mock).mockReturnValue({
currentConfirmation: transactionMeta,
});

renderHook(() => useMaxValueRefresher());

expect(dispatchMock).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
TransactionType,
type TransactionMeta,
} from '@metamask/transaction-controller';
import { add0x } from '@metamask/utils';

import {
getSelectedAccountCachedBalance,
selectMaxValueModeForTransaction,
} from '../../../../../../selectors';
import { subtractHexes } from '../../../../../../../shared/modules/conversion.utils';
import { updateEditableParams } from '../../../../../../store/actions';
import { useConfirmContext } from '../../../../context/confirm';
import { useFeeCalculations } from './useFeeCalculations';

// This hook is used to refresh the max value of the transaction
// when the user is in max amount mode only for the transaction type simpleSend
// It subtracts the native fee from the balance and updates the value of the transaction
export const useMaxValueRefresher = () => {
const { currentConfirmation: transactionMeta } =
useConfirmContext<TransactionMeta>();
const dispatch = useDispatch();
const { preciseNativeFeeInHex } = useFeeCalculations(transactionMeta);
const balance = useSelector(getSelectedAccountCachedBalance);
const isMaxAmountMode = useSelector((state) =>
selectMaxValueModeForTransaction(state, transactionMeta?.id),
);

useEffect(() => {
if (
!isMaxAmountMode ||
transactionMeta.type !== TransactionType.simpleSend
) {
return;
}

const newValue = subtractHexes(balance, preciseNativeFeeInHex);
const newValueInHex = add0x(newValue);

dispatch(
updateEditableParams(transactionMeta.id, { value: newValueInHex }),
);
}, [isMaxAmountMode, balance, preciseNativeFeeInHex]);
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { TransactionMeta } from '@metamask/transaction-controller';
import React from 'react';
import { TransactionMeta } from '@metamask/transaction-controller';
import { useConfirmContext } from '../../../../context/confirm';
import { SimulationDetails } from '../../../simulation-details';
import { AdvancedDetails } from '../shared/advanced-details/advanced-details';
import { GasFeesSection } from '../shared/gas-fees-section/gas-fees-section';
import NativeSendHeading from '../shared/native-send-heading/native-send-heading';
import { TokenDetailsSection } from '../token-transfer/token-details-section';
import { TransactionFlowSection } from '../token-transfer/transaction-flow-section';
import { useMaxValueRefresher } from '../hooks/useMaxValueRefresher';

const NativeTransferInfo = () => {
const { currentConfirmation: transactionMeta } =
useConfirmContext<TransactionMeta>();
useMaxValueRefresher();

const isWalletInitiated = transactionMeta.origin === 'metamask';

Expand Down
7 changes: 7 additions & 0 deletions ui/selectors/confirm-transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,10 @@ export const selectTransactionValue = createSelector(
(isMaxValueEnabled, maxValue, transactionMetadata) =>
isMaxValueEnabled ? maxValue : transactionMetadata?.txParams?.value,
);

const maxValueModeSelector = (state) => state.confirmTransaction.maxValueMode;

export function selectMaxValueModeForTransaction(state, transactionId) {
const maxValueModes = maxValueModeSelector(state);
return maxValueModes[transactionId];
}

0 comments on commit 180641b

Please sign in to comment.