diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b657fd1158..2688a9e67bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,55 @@ ## Current Main Branch +## 7.2.0 - Jun 05, 2023 +### Added +- [#6632](https://github.com/MetaMask/metamask-mobile/pull/6632): feat: add linea mainnet alert message +- [#6496](https://github.com/MetaMask/metamask-mobile/pull/6496): feat(551): add Linea Mainnet +- [#6494](https://github.com/MetaMask/metamask-mobile/pull/6494): feat: Update banner component to show/hide details section +- [#6539](https://github.com/MetaMask/metamask-mobile/pull/6539): feat: [MC 0.5] Remove drawer and add remain options to settings tab +- [#6378](https://github.com/MetaMask/metamask-mobile/pull/6378): feat: Add eth_sign friction +- [#6534](https://github.com/MetaMask/metamask-mobile/pull/6534): feat(action): remove labels after issue closed +- [#6570](https://github.com/MetaMask/metamask-mobile/pull/6570): feat: Translations for the disconnected account toast +- [#6452](https://github.com/MetaMask/metamask-mobile/pull/6452): feat: [MC 0.5] - Add Account management actions +- [#5591](https://github.com/MetaMask/metamask-mobile/pull/5591): feat: Custom Spend Allowance +- [#6426](https://github.com/MetaMask/metamask-mobile/pull/6426): feat: Componentize ListItem +- [#6514](https://github.com/MetaMask/metamask-mobile/pull/6514): feat: Componentize BottomSheetFooter +- [#6466](https://github.com/MetaMask/metamask-mobile/pull/6466): feat: componentize BottomSheetHeader +- [#6294](https://github.com/MetaMask/metamask-mobile/pull/6294): feat: [MC 0.5] - Activity view and Settings on the tab bar +- [#6486](https://github.com/MetaMask/metamask-mobile/pull/6486): feat: Add disabled prop on base button + +### Changed +- [#6612](https://github.com/MetaMask/metamask-mobile/pull/6612): chore: approve txn when gas estimation ready +- [#6054](https://github.com/MetaMask/metamask-mobile/pull/6054): chore: Improve TagURL +- [#6520](https://github.com/MetaMask/metamask-mobile/pull/6520): chore: improve variable name +- [#6597](https://github.com/MetaMask/metamask-mobile/pull/6597): chore: rm unused prepareFullTransaction +- [#6291](https://github.com/MetaMask/metamask-mobile/pull/6291): refactor: trigger transaction modals using approval requests +- [#5751](https://github.com/MetaMask/metamask-mobile/pull/5751): chore: Keystone links +- [#6541](https://github.com/MetaMask/metamask-mobile/pull/6541): chore: Delete an unused hook +- [#6530](https://github.com/MetaMask/metamask-mobile/pull/6530): chore: pending review feedback for token details related changes +- [#6401](https://github.com/MetaMask/metamask-mobile/pull/6401): refactor: handle watch asset accept and reject using ApprovalController only +- [#6529](https://github.com/MetaMask/metamask-mobile/pull/6529): chore: adding english string for advanced settings eth_sign warning +- [#6026](https://github.com/MetaMask/metamask-mobile/pull/6026): chore: Add toggle to enable/disable multi account balances fetching +- [#6512](https://github.com/MetaMask/metamask-mobile/pull/6512): chore: upgrade to cocoapods 1.12.0 +- [#6487](https://github.com/MetaMask/metamask-mobile/pull/6487): chore: new Show test networks translation +- [#6357](https://github.com/MetaMask/metamask-mobile/pull/6357): refactor: use approval controller for watch asset confirmation + +### Fixed +- [#6549](https://github.com/MetaMask/metamask-mobile/pull/6549): fix: Networks text alignement +- [#6634](https://github.com/MetaMask/metamask-mobile/pull/6634): fix: disable next button if custom input is invalid +- [#6491](https://github.com/MetaMask/metamask-mobile/pull/6491): fix: refactor linea testnet implementation +- [#6358](https://github.com/MetaMask/metamask-mobile/pull/6358): fix: No Warning appears when a Dapp sets a really high Fees for a tx, potentially loosing all user funds +- [#6592](https://github.com/MetaMask/metamask-mobile/pull/6592): fix: Nonce too low error on Approve ERC20 and ERC721 transactions +- [#6577](https://github.com/MetaMask/metamask-mobile/pull/6577): fix: onBoarding wizard horizontal alignment on step1 and on browser step +- [#6598](https://github.com/MetaMask/metamask-mobile/pull/6598): fix: Hold to reveal Spanish copy +- [#6523](https://github.com/MetaMask/metamask-mobile/pull/6523): fix: Network logo to represent first letter of network +- [#6560](https://github.com/MetaMask/metamask-mobile/pull/6560): fix: asset page header transition +- [#6473](https://github.com/MetaMask/metamask-mobile/pull/6473): fix: fix for swaps button displaying on unsupported networks +- [#6464](https://github.com/MetaMask/metamask-mobile/pull/6464): fix: bug domain not shown on signature +- [#6517](https://github.com/MetaMask/metamask-mobile/pull/6517): fix: remove duplicate ganache steps definitions +- [#6299](https://github.com/MetaMask/metamask-mobile/pull/6299): fix: for from address balance shown for ERC20 transfers +- [#6471](https://github.com/MetaMask/metamask-mobile/pull/6471): fix: Approve default ERC20 + ## 7.1.0 - Jun 20, 2023 - [#6334](https://github.com/MetaMask/metamask-mobile/pull/6334): feat: Aurora Token Detection - [#6351](https://github.com/MetaMask/metamask-mobile/pull/6351): feat: use thunk to handle processed order side effects diff --git a/android/app/build.gradle b/android/app/build.gradle index ab5b848c8d6..c35e65edbc1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -132,12 +132,13 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion namespace"io.metamask" + defaultConfig { - applicationId "io.metamask" + applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1136 - versionName "7.1.0" + versionCode 1142 + versionName "7.2.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.constants.ts b/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.constants.ts index 0584b27380c..2364089e924 100644 --- a/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.constants.ts +++ b/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.constants.ts @@ -1,3 +1,4 @@ -const CUSTOM_INPUT_TEST_ID = 'custom-input-test-id'; - -export default CUSTOM_INPUT_TEST_ID; +export const CUSTOM_SPEND_CAP_INPUT_TEST_ID = 'custom-spend-cap-input-test-id'; +export const CUSTOM_SPEND_CAP_MAX_TEST_ID = 'custom-spend-cap-max-test-id'; +export const CUSTOM_SPEND_CAP_INPUT_INPUT_ID = + 'custom-spend-cap-input-input-id'; diff --git a/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.test.tsx b/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.test.tsx index d5548f0386b..00e11f8fff7 100644 --- a/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.test.tsx +++ b/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.test.tsx @@ -6,6 +6,10 @@ import React from 'react'; import { TICKER } from '../CustomSpendCap.constants'; // Internal dependencies. import CustomInput from './CustomInput'; +import { + CUSTOM_SPEND_CAP_INPUT_INPUT_ID, + CUSTOM_SPEND_CAP_MAX_TEST_ID, +} from './CustomInput.constants'; import { CustomInputProps } from './CustomInput.types'; describe('CustomInput', () => { @@ -19,6 +23,7 @@ describe('CustomInput', () => { isEditDisabled: false, setMaxSelected: jest.fn(), setValue: jest.fn(), + tokenDecimal: 4, }; }); @@ -28,4 +33,42 @@ describe('CustomInput', () => { const component = renderComponent(); expect(component).toMatchSnapshot(); }); + + it('should call setMaxSelected when max button is pressed', () => { + const component = renderComponent(); + component + .findWhere((node) => node.prop('testID') === CUSTOM_SPEND_CAP_MAX_TEST_ID) + .simulate('press'); + expect(props.setMaxSelected).toHaveBeenCalled(); + }); + + it('should update value if input is integer', () => { + const component = renderComponent(); + component + .findWhere( + (node) => node.prop('testID') === CUSTOM_SPEND_CAP_INPUT_INPUT_ID, + ) + .simulate('changeText', '123'); + expect(props.setValue).toHaveBeenCalledWith('123'); + }); + + it('should update value if input is decimal and decimal points are less than or equal to tokenDecimal', () => { + const component = renderComponent(); + component + .findWhere( + (node) => node.prop('testID') === CUSTOM_SPEND_CAP_INPUT_INPUT_ID, + ) + .simulate('changeText', '123.1234'); + expect(props.setValue).toHaveBeenCalledWith('123.1234'); + }); + + it('should not update value if input is decimal and decimal points are greater than tokenDecimal', () => { + const component = renderComponent(); + component + .findWhere( + (node) => node.prop('testID') === CUSTOM_SPEND_CAP_INPUT_INPUT_ID, + ) + .simulate('changeText', '123.1234567'); + expect(props.setValue).not.toHaveBeenCalled(); + }); }); diff --git a/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.tsx b/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.tsx index 6e419bf8125..a2fb7c71a75 100644 --- a/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.tsx +++ b/app/component-library/components-temp/CustomSpendCap/CustomInput/CustomInput.tsx @@ -8,7 +8,11 @@ import { dotAndCommaDecimalFormatter } from '../../../../util/number'; import Text, { TextVariant } from '../../../components/Texts/Text'; // External dependencies. import { useStyles } from '../../../hooks'; -import CUSTOM_INPUT_TEST_ID from './CustomInput.constants'; +import { + CUSTOM_SPEND_CAP_INPUT_INPUT_ID, + CUSTOM_SPEND_CAP_INPUT_TEST_ID, + CUSTOM_SPEND_CAP_MAX_TEST_ID, +} from './CustomInput.constants'; import stylesheet from './CustomInput.styles'; // Internal dependencies. import { CustomInputProps } from './CustomInput.types'; @@ -20,8 +24,15 @@ const CustomInput = ({ isInputGreaterThanBalance, setValue, isEditDisabled, + tokenDecimal, }: CustomInputProps) => { const handleUpdate = (text: string) => { + const decimalIndex = text.indexOf('.'); + const fractionalLength = text.substring(decimalIndex + 1).length; + + if (decimalIndex !== -1 && fractionalLength > Number(tokenDecimal)) { + return; + } setValue(dotAndCommaDecimalFormatter(text)); }; @@ -49,11 +60,12 @@ const CustomInput = ({ backgroundColor: colors.background.alternative, }, ]} - testID={CUSTOM_INPUT_TEST_ID} + testID={CUSTOM_SPEND_CAP_INPUT_TEST_ID} > {!isEditDisabled ? ( {!isEditDisabled && ( @@ -57,6 +58,7 @@ exports[`CustomInput should render correctly 1`] = ` "color": "#535A61", } } + testID="custom-spend-cap-max-test-id" variant="sBodySM" > Max diff --git a/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.stories.tsx b/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.stories.tsx index 3f8b4d962c5..c4efb87a9d8 100644 --- a/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.stories.tsx +++ b/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.stories.tsx @@ -7,7 +7,6 @@ import CustomSpendCap from './CustomSpendCap'; // Internal dependencies. import { ACCOUNT_BALANCE, - DAPP_DOMAIN, DAPP_PROPOSED_VALUE, INPUT_VALUE_CHANGED, TICKER, @@ -18,10 +17,11 @@ storiesOf('Component Library / CustomSpendCap', module).add('Default', () => ( ticker={TICKER} accountBalance={ACCOUNT_BALANCE} dappProposedValue={DAPP_PROPOSED_VALUE} - domain={DAPP_DOMAIN} onInputChanged={INPUT_VALUE_CHANGED} isEditDisabled={false} editValue={() => undefined} tokenSpendValue={''} + toggleLearnMoreWebPage={() => undefined} + isInputValid={() => true} /> )); diff --git a/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.test.tsx b/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.test.tsx index 0a564f89879..e8d612600c1 100644 --- a/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.test.tsx +++ b/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.test.tsx @@ -7,7 +7,6 @@ import CustomSpendCap from './CustomSpendCap'; import { ACCOUNT_BALANCE, CUSTOM_SPEND_CAP_TEST_ID, - DAPP_DOMAIN, DAPP_PROPOSED_VALUE, INPUT_VALUE_CHANGED, TICKER, @@ -16,20 +15,22 @@ import { import { CustomSpendCapProps } from './CustomSpendCap.types'; function RenderCustomSpendCap( - tokenSpendValue: string, + tokenSpendValue = '', isInputValid: () => boolean = () => true, + dappProposedValue: string = DAPP_PROPOSED_VALUE, ) { return ( ({})} tokenSpendValue={tokenSpendValue} isInputValid={isInputValid} + tokenDecimal={18} + toggleLearnMoreWebPage={() => undefined} /> ); } @@ -63,7 +64,7 @@ describe('CustomSpendCap', () => { expect( await findByText( - `Only enter a number that you're comfortable with ${DAPP_DOMAIN} accessing now or in the future. You can always increase the token limit later.`, + `Only enter a number that you're comfortable with the third party spending now or in the future. You can always increase the spending cap later. Learn more`, ), ).toBeDefined(); }); @@ -81,13 +82,15 @@ describe('CustomSpendCap', () => { it('should render valid message if value is greater than account balance', async () => { const valueGreaterThanBalance = '300'; - const valueDifference = - Number(valueGreaterThanBalance) - Number(ACCOUNT_BALANCE); - const { toJSON } = renderWithProvider( + const { findByText } = renderWithProvider( RenderCustomSpendCap(valueGreaterThanBalance), ); - expect(JSON.stringify(toJSON())).toMatch(`${valueDifference} ${TICKER}`); + expect( + await findByText( + 'This allows the third party to spend all your token balance until it reaches the cap or you revoke the spending cap. If this is not intended, consider setting a lower spending cap. Learn more', + ), + ).toBeDefined(); }); it('should call isInputValid with false if value is not a number', async () => { @@ -103,4 +106,18 @@ describe('CustomSpendCap', () => { expect(isInputValid).toHaveBeenCalledWith(true); }); + + it('should render token spend value if present', async () => { + const inputtedSpendValue = '100'; + + const { findByText } = renderWithProvider( + RenderCustomSpendCap( + inputtedSpendValue, + isInputValid, + DAPP_PROPOSED_VALUE, + ), + ); + + expect(await findByText(`${inputtedSpendValue} ${TICKER}`)).toBeDefined(); + }); }); diff --git a/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.tsx b/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.tsx index a49675af402..a31ef5379d3 100644 --- a/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.tsx +++ b/app/component-library/components-temp/CustomSpendCap/CustomSpendCap.tsx @@ -1,10 +1,10 @@ -import BigNumber from 'bignumber.js'; // Third party dependencies. import React, { useEffect, useState } from 'react'; import { Pressable, View } from 'react-native'; import { strings } from '../../../../locales/i18n'; import InfoModal from '../../../components/UI/Swaps/components/InfoModal'; +import { TOKEN_APPROVAL_SPENDING_CAP } from '../../../constants/urls'; import formatNumber from '../../../util/formatNumber'; import { isNumber } from '../../../util/number'; import Button, { ButtonVariants } from '../../components/Buttons/Button'; @@ -22,12 +22,13 @@ const CustomSpendCap = ({ ticker, dappProposedValue, accountBalance, - domain, onInputChanged, isEditDisabled, editValue, tokenSpendValue, isInputValid, + tokenDecimal, + toggleLearnMoreWebPage, }: CustomSpendCapProps) => { const { styles, @@ -58,6 +59,11 @@ const CustomSpendCap = ({ isInputValid(!inputHasError); }, [inputHasError, isInputValid]); + useEffect(() => { + const spendValue = tokenSpendValue || dappProposedValue; + setValue(spendValue); + }, [dappProposedValue, tokenSpendValue]); + const handleDefaultValue = () => { setMaxSelected(false); setValue(dappProposedValue); @@ -65,18 +71,13 @@ const CustomSpendCap = ({ }; const handlePress = () => { - if (isEditDisabled) editValue(); - handleDefaultValue(); + isEditDisabled ? editValue() : handleDefaultValue(); }; useEffect(() => { if (maxSelected) setValue(accountBalance); }, [maxSelected, accountBalance]); - const newValue = new BigNumber(value); - - const difference = newValue.minus(accountBalance).toFixed(); - useEffect(() => { if (Number(value) > Number(accountBalance)) return setInputValueHigherThanAccountBalance(true); @@ -94,22 +95,11 @@ const CustomSpendCap = ({ ); const NO_SELECTED = strings( - 'contract_allowance.custom_spend_cap.no_value_selected', - { domain }, + 'contract_allowance.custom_spend_cap.default_error_message', ); - const INPUT_VALUE_GREATER_THAN_ACCOUNT_BALANCE = ( - <> - {strings('contract_allowance.custom_spend_cap.this_contract_allows')} - - {` ${formatNumber(accountBalance)} ${ticker} `} - - {strings('contract_allowance.custom_spend_cap.from_your_current_balance')} - - {` ${formatNumber(difference)} ${ticker} `} - - {strings('contract_allowance.custom_spend_cap.future_tokens')} - + const INPUT_VALUE_GREATER_THAN_ACCOUNT_BALANCE = strings( + 'contract_allowance.custom_spend_cap.amount_greater_than_balance', ); const INPUT_VALUE_LOWER_THAN_ACCOUNT_BALANCE = ( @@ -155,6 +145,9 @@ const CustomSpendCap = ({ message = INPUT_VALUE_LOWER_THAN_ACCOUNT_BALANCE; } + const openLearnMore = () => + toggleLearnMoreWebPage(TOKEN_APPROVAL_SPENDING_CAP); + return ( {isModalVisible ? ( @@ -168,10 +161,7 @@ const CustomSpendCap = ({ 'contract_allowance.custom_spend_cap.info_modal_description_default', ) : strings( - 'contract_allowance.custom_spend_cap.no_value_selected', - { - domain, - }, + 'contract_allowance.custom_spend_cap.default_error_message', )} } @@ -206,7 +196,9 @@ const CustomSpendCap = ({ label={ isEditDisabled ? strings('contract_allowance.custom_spend_cap.edit') - : strings('contract_allowance.custom_spend_cap.use_default') + : strings( + 'contract_allowance.custom_spend_cap.use_site_suggestion', + ) } /> @@ -218,6 +210,7 @@ const CustomSpendCap = ({ setMaxSelected={setMaxSelected} value={value} isEditDisabled={isEditDisabled} + tokenDecimal={tokenDecimal} /> {value.length > 0 && inputHasError && ( @@ -228,7 +221,12 @@ const CustomSpendCap = ({ {!isEditDisabled && ( - {message} + {message}{' '} +