From d345de1874df08f6ce81558455e9be0693c06eb4 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Wed, 4 Sep 2024 14:47:38 +0100 Subject: [PATCH 01/20] chore: adds the note to trader flow and hides emoji for mmi --- app/scripts/controllers/app-state.js | 6 ++ app/scripts/metamask-controller.js | 2 + .../note-to-trader/note-to-trader.tsx | 70 +++++++++++++------ ui/hooks/useMMIConfirmations.ts | 4 +- ui/hooks/useMMICustodySendTransaction.ts | 48 +++++++++++++ .../components/confirm/footer/footer.tsx | 6 +- .../gas-timing/gas-timing.component.js | 3 +- ui/pages/confirmations/confirm/confirm.tsx | 6 ++ .../hooks/useCurrentConfirmation.ts | 3 +- ui/selectors/institutional/selectors.ts | 5 ++ .../institutional/institution-background.ts | 15 ++++ 11 files changed, 136 insertions(+), 32 deletions(-) diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 08b11facf9ca..12f1862b2783 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -531,6 +531,12 @@ export default class AppStateController extends EventEmitter { }); } + setNoteToTraderMessage(message) { + this.store.updateState({ + noteToTraderMessage: message + }); + } + ///: END:ONLY_INCLUDE_IF getSignatureSecurityAlertResponse(securityAlertId) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index be3f1d9c3228..06a8b9b7106f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3605,6 +3605,8 @@ export default class MetamaskController extends EventEmitter { ), setCustodianDeepLink: appStateController.setCustodianDeepLink.bind(appStateController), + setNoteToTraderMessage: + appStateController.setNoteToTraderMessage.bind(appStateController), ///: END:ONLY_INCLUDE_IF // snaps diff --git a/ui/components/institutional/note-to-trader/note-to-trader.tsx b/ui/components/institutional/note-to-trader/note-to-trader.tsx index fb2a7161b7ea..d6fa68a799fe 100644 --- a/ui/components/institutional/note-to-trader/note-to-trader.tsx +++ b/ui/components/institutional/note-to-trader/note-to-trader.tsx @@ -1,28 +1,54 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { TransactionMeta } from '@metamask/transaction-controller'; import { + BackgroundColor, + BorderRadius, Display, FlexDirection, JustifyContent, } from '../../../helpers/constants/design-system'; import { Label, Box, Text } from '../../component-library'; +import { setNoteToTraderMessage } from '../../../store/institutional/institution-background'; +import { + getIsNoteToTraderSupported, + State, +} from '../../../selectors/institutional/selectors'; +import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; +import { useConfirmContext } from '../../../pages/confirmations/context/confirm'; +import { getConfirmationSender } from '../../../pages/confirmations/components/confirm/utils'; +import { useI18nContext } from '../../../hooks/useI18nContext'; -type NoteToTraderProps = { - placeholder: string; - maxLength: number; - onChange: (value: string) => void; - noteText: string; - labelText: string; -}; +const NoteToTrader: React.FC = () => { + const dispatch = useDispatch(); + const t = useI18nContext(); + const [noteText, setNoteText] = useState(''); + + const { currentConfirmation } = useConfirmContext() as unknown as { + currentConfirmation: TransactionMeta | undefined; + }; + const { from } = getConfirmationSender(currentConfirmation); + const fromChecksumHexAddress = toChecksumHexAddress(from || ''); + const isNoteToTraderSupported = useSelector((state: State) => + getIsNoteToTraderSupported(state, fromChecksumHexAddress) + ); + + useEffect(() => { + const timer = setTimeout(() => { + dispatch(setNoteToTraderMessage(noteText)); + }, 700) + + return () => clearTimeout(timer) + }, [noteText]) -const NoteToTrader: React.FC = ({ - placeholder, - maxLength, - onChange, - noteText, - labelText, -}) => { return ( - + isNoteToTraderSupported ? + = ({ display={Display.Flex} justifyContent={JustifyContent.spaceBetween} > - + - {noteText.length}/{maxLength} + {noteText.length}/{280} = ({ + placeholder="The approver will see this note when approving the transaction at the custodian." + /> diff --git a/ui/components/institutional/note-to-trader/note-to-trader.test.tsx b/ui/components/institutional/note-to-trader/note-to-trader.test.tsx index 3be9812bdd38..2ee789cf68c7 100644 --- a/ui/components/institutional/note-to-trader/note-to-trader.test.tsx +++ b/ui/components/institutional/note-to-trader/note-to-trader.test.tsx @@ -1,24 +1,31 @@ -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; import React from 'react'; import NoteToTrader from './note-to-trader'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { renderWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; +import mockState from '../../../../test/data/mock-state.json'; + +jest.mock('../../../selectors/institutional/selectors', () => ({ + getIsNoteToTraderSupported: () => true, +})); + +const middleware = [thunk]; +const store = configureMockStore(middleware)(mockState); describe('NoteToTrader', () => { it('should render the Note to trader component', () => { - const props = { - placeholder: '', - maxLength: 280, - noteText: 'some text', - labelText: 'Transaction note', - onChange: jest.fn(), - }; + const { getByTestId, container } = renderWithConfirmContextProvider( + , + store, + ); - const { getByTestId, container } = render(); const transactionNoteInput = getByTestId( 'transaction-note', ) as HTMLInputElement; fireEvent.change(transactionNoteInput); - expect(transactionNoteInput.value).toBe('some text'); + expect(transactionNoteInput.value).toBe(''); expect(transactionNoteInput).toBeDefined(); expect(container).toMatchSnapshot(); }); diff --git a/ui/pages/confirmations/components/gas-timing/gas-timing.component.test.js b/ui/pages/confirmations/components/gas-timing/gas-timing.component.test.js index b318cd009180..14b25d925649 100644 --- a/ui/pages/confirmations/components/gas-timing/gas-timing.component.test.js +++ b/ui/pages/confirmations/components/gas-timing/gas-timing.component.test.js @@ -44,3 +44,29 @@ describe('Gas timing', () => { }); }); }); + +describe('will not render the emoji 🦊 when build type is mmi', () => { + beforeAll(() => { + jest.resetModules(); + process.env.METAMASK_BUILD_TYPE = 'mmi'; + }); + + afterAll(() => { + process.env.METAMASK_BUILD_TYPE = 'main'; + }); + + it('renders gas timing time when high estimate is chosen', async () => { + const mockStore = configureMockStore()(mockState); + + const props = { + maxPriorityFeePerGas: '1000000', + }; + + const screen = renderWithProvider(, mockStore); + + await waitFor(() => { + expect(screen.queryByText('Market')).toBeInTheDocument(); + expect(screen.getByTestId('gas-timing-time')).toBeInTheDocument(); + }); + }); +}); diff --git a/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts b/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts index 9d415d64a527..5f07cd1f67f6 100644 --- a/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts +++ b/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts @@ -310,31 +310,4 @@ describe('useCurrentConfirmation', () => { expect(currentConfirmation).toBeUndefined(); }); }); - - describe('useCurrentConfirmation with MM build type env var (MMI)', () => { - beforeAll(() => { - jest.resetModules(); - process.env.METAMASK_BUILD_TYPE = 'mmi'; - }); - - afterAll(() => { - process.env.METAMASK_BUILD_TYPE = 'main'; - }); - - it('returns undefined if build type is MMI, user setting is enabled and transaction has correct type', () => { - const currentConfirmation = runHook({ - pendingApprovals: [ - { ...APPROVAL_MOCK, type: ApprovalType.Transaction }, - ], - redesignedConfirmationsEnabled: true, - transaction: { - ...TRANSACTION_MOCK, - type: TransactionType.contractInteraction, - }, - isRedesignedConfirmationsDeveloperEnabled: false, - }); - - expect(currentConfirmation).toBeUndefined(); - }); - }); }); diff --git a/ui/selectors/institutional/selectors.test.ts b/ui/selectors/institutional/selectors.test.ts index d089607031b1..be617252b372 100644 --- a/ui/selectors/institutional/selectors.test.ts +++ b/ui/selectors/institutional/selectors.test.ts @@ -18,6 +18,7 @@ import { getMMIConfiguration, getInteractiveReplacementToken, getCustodianDeepLink, + getNoteToTraderMessage, getIsNoteToTraderSupported, MmiConfiguration, State, @@ -841,4 +842,29 @@ describe('Institutional selectors', () => { expect(isSupported).toBe(false); }); }); + + describe('getNoteToTraderMessage', () => { + it('returns noteToTraderMessage if it exists', () => { + const noteToTraderMessage = 'some message'; + const state = { + metamask: { + noteToTraderMessage, + }, + }; + + const token = getNoteToTraderMessage(state); + + expect(token).toStrictEqual(noteToTraderMessage); + }); + + it('returns an empty string if noteToTraderMessage does not exist', () => { + const state = { + metamask: {}, + }; + + const token = getNoteToTraderMessage(state); + + expect(token).toStrictEqual(''); + }); + }); }); diff --git a/ui/selectors/institutional/selectors.ts b/ui/selectors/institutional/selectors.ts index 9dd79864ea6c..18d7644eb63d 100644 --- a/ui/selectors/institutional/selectors.ts +++ b/ui/selectors/institutional/selectors.ts @@ -228,7 +228,7 @@ export function getIsNoteToTraderSupported( ) { const { custodyAccountDetails, mmiConfiguration } = state.metamask; const accountDetails = custodyAccountDetails?.[fromChecksumHexAddress]; - + console.log(accountDetails); if (!accountDetails) { return false; } @@ -236,7 +236,7 @@ export function getIsNoteToTraderSupported( const foundCustodian = mmiConfiguration?.custodians?.find( (custodian) => custodian.envName === accountDetails.custodianName, ); - + console.log(foundCustodian); return foundCustodian ? foundCustodian.isNoteToTraderSupported : false; } diff --git a/ui/store/institutional/institution-background.test.js b/ui/store/institutional/institution-background.test.js index c70b4677a4eb..7a3ca82e53eb 100644 --- a/ui/store/institutional/institution-background.test.js +++ b/ui/store/institutional/institution-background.test.js @@ -8,6 +8,7 @@ import { mmiActionsFactory, showInteractiveReplacementTokenBanner, setCustodianDeepLink, + setNoteToTraderMessage, setTypedMessageInProgress, setPersonalMessageInProgress, } from './institution-background'; @@ -159,4 +160,17 @@ describe('Institution Actions', () => { expect(hideLoadingIndication).toHaveBeenCalled(); }); }); + + describe('#setNoteToTraderMessage', () => { + it('should test setNoteToTraderMessage action', async () => { + const dispatch = jest.fn(); + + await setNoteToTraderMessage('some message')(dispatch); + + expect(submitRequestToBackground).toHaveBeenCalledWith( + 'setNoteToTraderMessage', + ['some message'], + ); + }); + }); }); From 39d1b9f955868c25622edff9600fd9f171e19f1d Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Wed, 4 Sep 2024 16:23:03 +0100 Subject: [PATCH 04/20] chore: lint fix --- .../note-to-trader/note-to-trader.test.tsx | 2 +- ui/hooks/useMMICustodySendTransaction.test.js | 11 +++++++++++ .../components/gas-timing/gas-timing.component.js | 2 +- ui/store/institutional/institution-background.ts | 10 ++++++---- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ui/components/institutional/note-to-trader/note-to-trader.test.tsx b/ui/components/institutional/note-to-trader/note-to-trader.test.tsx index 2ee789cf68c7..f5c7f0457ec2 100644 --- a/ui/components/institutional/note-to-trader/note-to-trader.test.tsx +++ b/ui/components/institutional/note-to-trader/note-to-trader.test.tsx @@ -1,10 +1,10 @@ import { fireEvent } from '@testing-library/react'; import React from 'react'; -import NoteToTrader from './note-to-trader'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { renderWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; import mockState from '../../../../test/data/mock-state.json'; +import NoteToTrader from './note-to-trader'; jest.mock('../../../selectors/institutional/selectors', () => ({ getIsNoteToTraderSupported: () => true, diff --git a/ui/hooks/useMMICustodySendTransaction.test.js b/ui/hooks/useMMICustodySendTransaction.test.js index a9cde9e26bf5..ecfe08fb4cc0 100644 --- a/ui/hooks/useMMICustodySendTransaction.test.js +++ b/ui/hooks/useMMICustodySendTransaction.test.js @@ -25,12 +25,23 @@ jest.mock('../store/institutional/institution-background', () => ({ jest.mock('../selectors', () => ({ getAccountType: jest.fn(), + getIsNoteToTraderSupported: () => true, })); jest.mock('../store/actions', () => ({ updateAndApproveTx: jest.fn(), })); +jest.mock('../../../pages/confirmations/context/confirm', () => ({ + useConfirmContext: () => ({ + currentConfirmation: { from: '0x123' }, + }), +})); + +jest.mock('../../../pages/confirmations/components/confirm/utils', () => ({ + getConfirmationSender: () => ({ from: '0x123' }), +})); + describe('useMMICustodySendTransaction', () => { it('handles custody account type', async () => { const dispatch = jest.fn(); diff --git a/ui/pages/confirmations/components/gas-timing/gas-timing.component.js b/ui/pages/confirmations/components/gas-timing/gas-timing.component.js index 1df3ec8a9343..51522e025add 100644 --- a/ui/pages/confirmations/components/gas-timing/gas-timing.component.js +++ b/ui/pages/confirmations/components/gas-timing/gas-timing.component.js @@ -133,7 +133,7 @@ export default function GasTiming({ const estimateToUse = estimateUsed || transactionData.userFeeLevel || 'medium'; - const estimateEmoji = !isMMI() ? PRIORITY_LEVEL_ICON_MAP[estimateToUse] : ''; + const estimateEmoji = isMMI() ? '' : PRIORITY_LEVEL_ICON_MAP[estimateToUse]; let text = `${estimateEmoji} ${t(estimateToUse)}`; let time = ''; diff --git a/ui/store/institutional/institution-background.ts b/ui/store/institutional/institution-background.ts index 467ca6afd8b3..26132a888daf 100644 --- a/ui/store/institutional/institution-background.ts +++ b/ui/store/institutional/institution-background.ts @@ -62,10 +62,12 @@ export function setNoteToTraderMessage(message: string) { await submitRequestToBackground('setNoteToTraderMessage', [message]); await forceUpdateMetamaskState(dispatch); - } catch (err: any) { - if (err) { - dispatch(displayWarning(err.message)); - throw new Error(err.message); + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error) { + dispatch(displayWarning(error.message)); + throw new Error(error.message); } } }; From 665691e09c490f1b7cc3fd94680c5a80264fe04c Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Thu, 5 Sep 2024 15:17:20 +0100 Subject: [PATCH 05/20] chore: test update --- ui/hooks/useMMICustodySendTransaction.test.js | 11 +++-------- ui/hooks/useMMICustodySendTransaction.ts | 4 +++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/ui/hooks/useMMICustodySendTransaction.test.js b/ui/hooks/useMMICustodySendTransaction.test.js index ecfe08fb4cc0..8e9d1f500a7c 100644 --- a/ui/hooks/useMMICustodySendTransaction.test.js +++ b/ui/hooks/useMMICustodySendTransaction.test.js @@ -7,7 +7,7 @@ import { useMMICustodySendTransaction } from './useMMICustodySendTransaction'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), - useHistory: jest.fn(), + useHistory: jest.fn().mockReturnValue({ push: jest.fn() }), })); jest.mock('react-redux', () => ({ @@ -23,22 +23,17 @@ jest.mock('../store/institutional/institution-background', () => ({ mmiActionsFactory: jest.fn(), })); -jest.mock('../selectors', () => ({ - getAccountType: jest.fn(), - getIsNoteToTraderSupported: () => true, -})); - jest.mock('../store/actions', () => ({ updateAndApproveTx: jest.fn(), })); -jest.mock('../../../pages/confirmations/context/confirm', () => ({ +jest.mock('../pages/confirmations/context/confirm', () => ({ useConfirmContext: () => ({ currentConfirmation: { from: '0x123' }, }), })); -jest.mock('../../../pages/confirmations/components/confirm/utils', () => ({ +jest.mock('../pages/confirmations/components/confirm/utils', () => ({ getConfirmationSender: () => ({ from: '0x123' }), })); diff --git a/ui/hooks/useMMICustodySendTransaction.ts b/ui/hooks/useMMICustodySendTransaction.ts index ed1765e2bd22..c54820400358 100644 --- a/ui/hooks/useMMICustodySendTransaction.ts +++ b/ui/hooks/useMMICustodySendTransaction.ts @@ -55,7 +55,9 @@ export function useMMICustodySendTransaction() { const isSmartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); - const { chainId, rpcUrl: customRpcUrl } = useSelector(getProviderConfig); + const { chainId, rpcUrl: customRpcUrl } = + useSelector(getProviderConfig) || {}; + const builtinRpcUrl = CHAIN_ID_TO_RPC_URL_MAP[chainId as keyof typeof CHAIN_ID_TO_RPC_URL_MAP]; From 6dd7f1866fb5224da11a4fdfc383134ce9ba7ac5 Mon Sep 17 00:00:00 2001 From: Antonio Regadas Date: Mon, 16 Sep 2024 14:29:50 +0100 Subject: [PATCH 06/20] chore: ads back old note-to-trader for compatibility --- .../note-to-trader/note-to-trader-tabbed.tsx | 61 +++++++++++++++++++ .../note-to-trader/note-to-trader.tsx | 5 +- .../confirm-transaction-base.component.js | 4 +- ui/selectors/institutional/selectors.ts | 4 +- 4 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 ui/components/institutional/note-to-trader/note-to-trader-tabbed.tsx diff --git a/ui/components/institutional/note-to-trader/note-to-trader-tabbed.tsx b/ui/components/institutional/note-to-trader/note-to-trader-tabbed.tsx new file mode 100644 index 000000000000..7134de7670c1 --- /dev/null +++ b/ui/components/institutional/note-to-trader/note-to-trader-tabbed.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { + Display, + FlexDirection, + JustifyContent, +} from '../../../helpers/constants/design-system'; +import { Label, Box, Text } from '../../component-library'; + +type NoteToTraderProps = { + placeholder: string; + maxLength: number; + onChange: (value: string) => void; + noteText: string; + labelText: string; +}; + +const TabbedNoteToTrader: React.FC = ({ + placeholder, + maxLength, + onChange, + noteText, + labelText, +}) => { + return ( + + + + + + {noteText.length}/{maxLength} + + + +