From 33aa8678c9b9889375598cc5b7a4cd24f6613500 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 22 Jul 2025 15:50:51 +0000 Subject: [PATCH] chore(runway): cherry-pick chore: cp-7.51.0 faster address checksum (#16926) (#17174) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This [PR](https://github.com/MetaMask/metamask-mobile/pull/16926) is originally from @Nodonisko from Margelo This PR uses new faster memoized functions for working with address checksum. There is already significant boost in AccountList performance and it should be even better when https://github.com/MetaMask/core/pull/6054 is released and also when we fix Blockies Avatars. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/15560 ## **Manual testing steps** 1. Open Account List 2. It should feel faster than before ## **Screenshots/Recordings** ### **Before** You can set that before then was lot of interrupted work before Account List modal is even displayed. It takes around ~7s from click to modal to be actually visible. Screenshot 2025-07-03 at 21 36 06 ### **After** Here it takes only around ~3s to mount. (still slow but much better than before) Screenshot 2025-07-03 at 21 38 16 ## **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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. ## **Description** ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. Co-authored-by: Daniel Suchý --- .../AggregatedPercentageCrossChains.test.tsx | 5 +++- .../AggregatedPercentageCrossChains.tsx | 3 +- .../UI/AccountFromToInfoCard/AddressFrom.tsx | 2 +- .../hooks/useTokensWithBalance/index.ts | 7 +++-- .../UI/PaymentRequest/AssetList/index.tsx | 2 +- app/components/UI/TransactionElement/utils.js | 2 +- .../Settings/Contacts/ContactForm/index.js | 2 +- .../confirmations/hooks/useAccountInfo.ts | 5 ++-- .../Views/confirmations/legacy/Send/index.js | 7 +++-- .../legacy/SendFlow/SendTo/index.js | 2 +- .../ApproveTransactionHeader.tsx | 2 +- .../AddNickname/index.tsx | 2 +- .../hooks/useAccounts/useAccounts.test.ts | 7 ++--- app/components/hooks/useExistingAddress.ts | 3 +- app/selectors/earnController/earn/index.ts | 14 ++++------ app/util/address/index.test.ts | 11 ++++++++ app/util/address/index.ts | 28 +++++++++++++++++-- app/util/checkAddress.ts | 2 +- app/util/transactions/index.js | 4 +-- 19 files changed, 72 insertions(+), 38 deletions(-) diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.test.tsx b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.test.tsx index 9cdeb6a1d5fe..02789d7362c3 100644 --- a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.test.tsx +++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.test.tsx @@ -23,10 +23,13 @@ jest.mock('../../../../selectors/currencyRateController', () => ({ })); jest.mock('ethereumjs-util', () => ({ - toChecksumAddress: jest.fn((address) => address), zeroAddress: jest.fn(() => '0x0000000000000000000000000000000000000000'), })); +jest.mock('../../../../util/address', () => ({ + toChecksumAddress: jest.fn((address) => address), +})); + describe('AggregatedPercentageCrossChains', () => { const mockStore = configureStore([]); let store: Store; diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.tsx b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.tsx index 202ba3663903..9a839918e94e 100644 --- a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.tsx +++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains.tsx @@ -10,7 +10,7 @@ import { FORMATTED_VALUE_PRICE_TEST_ID, FORMATTED_PERCENTAGE_TEST_ID, } from './AggregatedPercentage.constants'; -import { toChecksumAddress, zeroAddress } from 'ethereumjs-util'; +import { zeroAddress } from 'ethereumjs-util'; import { selectTokenMarketData } from '../../../../selectors/tokenRatesController'; import { MarketDataMapping, @@ -18,6 +18,7 @@ import { } from '../../../../components/hooks/useGetFormattedTokensPerChain'; import { getFormattedAmountChange, getPercentageTextColor } from './utils'; import { AggregatedPercentageCrossChainsProps } from './AggregatedPercentageCrossChains.types'; +import { toChecksumAddress } from '../../../../util/address'; export const getCalculatedTokenAmount1dAgo = ( tokenFiatBalance: number, diff --git a/app/components/UI/AccountFromToInfoCard/AddressFrom.tsx b/app/components/UI/AccountFromToInfoCard/AddressFrom.tsx index 1010dbfcb127..0f215fbbf1c0 100644 --- a/app/components/UI/AccountFromToInfoCard/AddressFrom.tsx +++ b/app/components/UI/AccountFromToInfoCard/AddressFrom.tsx @@ -1,4 +1,3 @@ -import { toChecksumAddress } from 'ethereumjs-util'; import React, { useEffect, useState } from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; @@ -16,6 +15,7 @@ import { import { getLabelTextByAddress, renderAccountName, + toChecksumAddress, } from '../../../util/address'; import useAddressBalance from '../../hooks/useAddressBalance/useAddressBalance'; import stylesheet from './AddressFrom.styles'; diff --git a/app/components/UI/Bridge/hooks/useTokensWithBalance/index.ts b/app/components/UI/Bridge/hooks/useTokensWithBalance/index.ts index fbfc147a4529..7dd8834a97cd 100644 --- a/app/components/UI/Bridge/hooks/useTokensWithBalance/index.ts +++ b/app/components/UI/Bridge/hooks/useTokensWithBalance/index.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { CaipChainId, getChecksumAddress, Hex } from '@metamask/utils'; +import { CaipChainId, Hex } from '@metamask/utils'; import { TokenI } from '../../../Tokens/types'; import { selectTokensBalances } from '../../../../../selectors/tokenBalancesController'; import { @@ -30,6 +30,7 @@ import { renderNumber, renderFiat } from '../../../../../util/number'; import { formatUnits } from 'ethers/lib/utils'; import { BigNumber } from 'ethers'; import { selectAccountsByChainId } from '../../../../../selectors/accountTrackerController'; +import { toChecksumAddress } from '../../../../../util/address'; interface CalculateFiatBalancesParams { assets: TokenI[]; @@ -90,7 +91,7 @@ export const calculateEvmBalances = ({ // Native EVM token if (token.isETH || token.isNative) { const nativeTokenBalanceAtomicHex = - evmAccountsByChainId?.[chainId]?.[getChecksumAddress(selectedAddress)] + evmAccountsByChainId?.[chainId]?.[toChecksumAddress(selectedAddress)] ?.balance || '0x0'; const nativeTokenBalance = formatUnits( BigNumber.from(nativeTokenBalanceAtomicHex), @@ -116,7 +117,7 @@ export const calculateEvmBalances = ({ ); const erc20BalanceAtomicHex = multiChainTokenBalances?.[token.address as Hex] || - multiChainTokenBalances?.[getChecksumAddress(token.address as Hex)] || + multiChainTokenBalances?.[toChecksumAddress(token.address as Hex)] || '0x0'; const erc20Balance = formatUnits( diff --git a/app/components/UI/PaymentRequest/AssetList/index.tsx b/app/components/UI/PaymentRequest/AssetList/index.tsx index c9482ff1192a..1f4638c66766 100644 --- a/app/components/UI/PaymentRequest/AssetList/index.tsx +++ b/app/components/UI/PaymentRequest/AssetList/index.tsx @@ -6,10 +6,10 @@ import { fontStyles } from '../../../../styles/common'; import Identicon from '../../Identicon'; import NetworkMainAssetLogo from '../../NetworkMainAssetLogo'; import { useSelector } from 'react-redux'; -import { toChecksumAddress } from 'ethereumjs-util'; import { useTheme } from '../../../../util/theme'; import { selectTokenList } from '../../../../selectors/tokenListController'; import { ImportTokenViewSelectorsIDs } from '../../../../../e2e/selectors/wallet/ImportTokenView.selectors'; +import { toChecksumAddress } from '../../../../util/address'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/app/components/UI/TransactionElement/utils.js b/app/components/UI/TransactionElement/utils.js index 781888014a66..6632f47f3fdd 100644 --- a/app/components/UI/TransactionElement/utils.js +++ b/app/components/UI/TransactionElement/utils.js @@ -16,6 +16,7 @@ import { renderFullAddress, areAddressesEqual, toFormattedAddress, + toChecksumAddress, } from '../../../util/address'; import { decodeTransferData, @@ -23,7 +24,6 @@ import { getActionKey, TRANSACTION_TYPES, } from '../../../util/transactions'; -import { toChecksumAddress } from 'ethereumjs-util'; import { swapsUtils } from '@metamask/swaps-controller'; import { isSwapsNativeAsset } from '../Swaps/utils'; import Engine from '../../../core/Engine'; diff --git a/app/components/Views/Settings/Contacts/ContactForm/index.js b/app/components/Views/Settings/Contacts/ContactForm/index.js index 1239c04f3d35..f5cab196a0ba 100644 --- a/app/components/Views/Settings/Contacts/ContactForm/index.js +++ b/app/components/Views/Settings/Contacts/ContactForm/index.js @@ -13,7 +13,6 @@ import PropTypes from 'prop-types'; import { getEditableOptions } from '../../../../UI/Navbar'; import StyledButton from '../../../../UI/StyledButton'; import Engine from '../../../../../core/Engine'; -import { toChecksumAddress } from 'ethereumjs-util'; import { connect } from 'react-redux'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { strings } from '../../../../../../locales/i18n'; @@ -21,6 +20,7 @@ import { renderShortAddress, areAddressesEqual, validateAddressOrENS, + toChecksumAddress, } from '../../../../../util/address'; import ErrorMessage from '../../../confirmations/legacy/SendFlow/ErrorMessage'; import AntIcon from 'react-native-vector-icons/AntDesign'; diff --git a/app/components/Views/confirmations/hooks/useAccountInfo.ts b/app/components/Views/confirmations/hooks/useAccountInfo.ts index dcd9a9686da2..19e23088fc1d 100644 --- a/app/components/Views/confirmations/hooks/useAccountInfo.ts +++ b/app/components/Views/confirmations/hooks/useAccountInfo.ts @@ -1,4 +1,3 @@ -import { toChecksumAddress } from 'ethereumjs-util'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; @@ -6,14 +5,14 @@ import { Hex } from '@metamask/utils'; import Engine from '../../../../core/Engine'; import useAddressBalance from '../../../../components/hooks/useAddressBalance/useAddressBalance'; import { selectInternalAccounts } from '../../../../selectors/accountsController'; -import { renderAccountName } from '../../../../util/address'; +import { renderAccountName, toChecksumAddress } from '../../../../util/address'; import { selectCurrentCurrency } from '../../../../selectors/currencyRateController'; import { formatWithThreshold } from '../../../../util/assets'; import I18n from '../../../../../locales/i18n'; const useAccountInfo = (address: string, chainId: Hex) => { const internalAccounts = useSelector(selectInternalAccounts); - const activeAddress = toChecksumAddress(address); + const activeAddress = toChecksumAddress(address as Hex); const { addressBalance: accountBalance } = useAddressBalance( undefined, address, diff --git a/app/components/Views/confirmations/legacy/Send/index.js b/app/components/Views/confirmations/legacy/Send/index.js index 8a83319ea7ba..63a8faa5189a 100644 --- a/app/components/Views/confirmations/legacy/Send/index.js +++ b/app/components/Views/confirmations/legacy/Send/index.js @@ -17,7 +17,6 @@ import { fromWei, fromTokenMinimalUnit, } from '../../../../../util/number'; -import { toChecksumAddress } from 'ethereumjs-util'; import { strings } from '../../../../../../locales/i18n'; import { getTransactionOptionsTitle } from '../../../../UI/Navbar'; import { connect } from 'react-redux'; @@ -36,7 +35,11 @@ import { generateTransferData, } from '../../../../../util/transactions'; import Logger from '../../../../../util/Logger'; -import { getAddress, areAddressesEqual } from '../../../../../util/address'; +import { + getAddress, + areAddressesEqual, + toChecksumAddress, +} from '../../../../../util/address'; import { MAINNET } from '../../../../../constants/network'; import BigNumber from 'bignumber.js'; import { WalletDevice } from '@metamask/transaction-controller'; diff --git a/app/components/Views/confirmations/legacy/SendFlow/SendTo/index.js b/app/components/Views/confirmations/legacy/SendFlow/SendTo/index.js index d8ea02a2030a..92170aadefef 100644 --- a/app/components/Views/confirmations/legacy/SendFlow/SendTo/index.js +++ b/app/components/Views/confirmations/legacy/SendFlow/SendTo/index.js @@ -2,7 +2,6 @@ import React, { Fragment, PureComponent } from 'react'; import { View, ScrollView, Alert, Platform, BackHandler } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { toChecksumAddress } from 'ethereumjs-util'; import { SafeAreaView } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/FontAwesome'; import AddressList from '../AddressList'; @@ -21,6 +20,7 @@ import { isValidHexAddress, validateAddressOrENS, areAddressesEqual, + toChecksumAddress, } from '../../../../../../util/address'; import { getEther, getTicker } from '../../../../../../util/transactions'; import { diff --git a/app/components/Views/confirmations/legacy/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx b/app/components/Views/confirmations/legacy/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx index 998ab03d45a3..a17543cf0b80 100644 --- a/app/components/Views/confirmations/legacy/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx +++ b/app/components/Views/confirmations/legacy/components/ApproveTransactionHeader/ApproveTransactionHeader.tsx @@ -1,4 +1,3 @@ -import { toChecksumAddress } from 'ethereumjs-util'; import React, { useEffect, useState } from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; @@ -12,6 +11,7 @@ import { selectAccountsByChainId } from '../../../../../../selectors/accountTrac import { getLabelTextByAddress, renderAccountName, + toChecksumAddress, } from '../../../../../../util/address'; import useAddressBalance from '../../../../../hooks/useAddressBalance/useAddressBalance'; import { diff --git a/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/AddNickname/index.tsx b/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/AddNickname/index.tsx index e71c8e594214..d713b17a2df3 100644 --- a/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/AddNickname/index.tsx +++ b/app/components/Views/confirmations/legacy/components/ApproveTransactionReview/AddNickname/index.tsx @@ -5,7 +5,6 @@ import EthereumAddress from '../../../../../../UI/EthereumAddress'; import Engine from '../../../../../../../core/Engine'; import { MetaMetricsEvents } from '../../../../../../../core/Analytics'; -import { toChecksumAddress } from 'ethereumjs-util'; import { connect, useSelector } from 'react-redux'; import StyledButton from '../../../../../../UI/StyledButton'; import Text from '../../../../../../../component-library/components/Texts/Text'; @@ -24,6 +23,7 @@ import { AddNicknameProps } from './types'; import { validateAddressOrENS, shouldShowBlockExplorer, + toChecksumAddress, } from '../../../../../../../util/address'; import ErrorMessage from '../../../SendFlow/ErrorMessage'; import { diff --git a/app/components/hooks/useAccounts/useAccounts.test.ts b/app/components/hooks/useAccounts/useAccounts.test.ts index 8f78435a626a..6b43597ae0b8 100644 --- a/app/components/hooks/useAccounts/useAccounts.test.ts +++ b/app/components/hooks/useAccounts/useAccounts.test.ts @@ -1,14 +1,13 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { KeyringTypes } from '@metamask/keyring-controller'; import { EthScope } from '@metamask/keyring-api'; -import { toChecksumAddress } from 'ethereumjs-util'; import useAccounts from './useAccounts'; import { backgroundState } from '../../../util/test/initial-root-state'; import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../util/test/accountsControllerTestUtils'; import { Account } from './useAccounts.types'; -import { Hex } from '@metamask/utils'; // eslint-disable-next-line import/no-namespace import * as networks from '../../../util/networks'; +import { toChecksumAddress } from '../../../util/address'; jest.mock('../../../core/Engine', () => ({ getTotalEvmFiatAccountBalance: jest.fn().mockReturnValue({ @@ -32,7 +31,7 @@ const MOCK_ACCOUNTS = Object.values( const MOCK_ACCOUNT_1: Account = { id: MOCK_ACCOUNTS[0].id, name: 'Account 1', - address: toChecksumAddress(MOCK_ACCOUNTS[0].address) as Hex, + address: toChecksumAddress(MOCK_ACCOUNTS[0].address), type: KeyringTypes.hd, yOffset: 0, isSelected: false, @@ -47,7 +46,7 @@ const MOCK_ACCOUNT_1: Account = { const MOCK_ACCOUNT_2: Account = { id: MOCK_ACCOUNTS[1].id, name: 'Account 2', - address: toChecksumAddress(MOCK_ACCOUNTS[1].address) as Hex, + address: toChecksumAddress(MOCK_ACCOUNTS[1].address), type: KeyringTypes.hd, yOffset: 78, isSelected: true, diff --git a/app/components/hooks/useExistingAddress.ts b/app/components/hooks/useExistingAddress.ts index c67c2ab4b387..100055af144b 100644 --- a/app/components/hooks/useExistingAddress.ts +++ b/app/components/hooks/useExistingAddress.ts @@ -1,8 +1,7 @@ -import { toChecksumAddress } from 'ethereumjs-util'; import { useSelector } from 'react-redux'; import { selectInternalAccounts } from '../../selectors/accountsController'; -import { areAddressesEqual } from '../../util/address'; +import { areAddressesEqual, toChecksumAddress } from '../../util/address'; import { AddressBookEntry } from '@metamask/address-book-controller'; import { selectAddressBook } from '../../selectors/addressBookController'; import { selectIsEvmNetworkSelected } from '../../selectors/multichainNetworkController'; diff --git a/app/selectors/earnController/earn/index.ts b/app/selectors/earnController/earn/index.ts index a17c536ff068..963823477c82 100644 --- a/app/selectors/earnController/earn/index.ts +++ b/app/selectors/earnController/earn/index.ts @@ -28,14 +28,13 @@ import { selectNetworkConfigurations } from '../../networkController'; import { selectTokensBalances } from '../../tokenBalancesController'; import { selectTokenMarketData } from '../../tokenRatesController'; import { pooledStakingSelectors } from '../pooledStaking'; -import { toChecksumAddress } from 'ethereumjs-util'; import { selectPooledStakingEnabledFlag, selectStablecoinLendingEnabledFlag, } from '../../../components/UI/Earn/selectors/featureFlags'; import { EarnTokenDetails } from '../../../components/UI/Earn/types/lending.types'; -import { isNonEvmAddress } from '../../../core/Multichain/utils'; import { createDeepEqualSelector } from '../../util'; +import { toFormattedAddress } from '../../../util/address'; const selectEarnControllerState = (state: RootState) => state.engine.backgroundState.EarnController; @@ -157,7 +156,6 @@ const selectEarnTokens = createDeepEqualSelector( selectLendingMarketsByChainIdAndTokenAddress(earnState); const lendingMarketsByChainIdAndOutputTokenAddress = selectLendingMarketsByChainIdAndOutputTokenAddress(earnState); - const isEvmAddress = !isNonEvmAddress(selectedAddress || ''); const earnTokensData = allTokens.reduce((acc, token) => { const experiences: EarnTokenDetails['experiences'] = []; @@ -192,15 +190,13 @@ const selectEarnTokens = createDeepEqualSelector( } // TODO: balance logic, extract to utils then use when we are clear to add token + const formattedAddress = toFormattedAddress(selectedAddress as Hex); const rawAccountBalance = selectedAddress - ? accountsByChainId[token?.chainId as Hex]?.[ - isEvmAddress ? toChecksumAddress(selectedAddress) : selectedAddress - ]?.balance + ? accountsByChainId[token?.chainId as Hex]?.[formattedAddress]?.balance : '0'; const rawStakedAccountBalance = selectedAddress - ? accountsByChainId[token?.chainId as Hex]?.[ - isEvmAddress ? toChecksumAddress(selectedAddress) : selectedAddress - ]?.stakedBalance + ? accountsByChainId[token?.chainId as Hex]?.[formattedAddress] + ?.stakedBalance : '0'; const balanceWei = hexToBN(rawAccountBalance); const stakedBalanceWei = hexToBN(rawStakedAccountBalance); diff --git a/app/util/address/index.test.ts b/app/util/address/index.test.ts index cc2931f23229..6e12ab83152a 100644 --- a/app/util/address/index.test.ts +++ b/app/util/address/index.test.ts @@ -22,6 +22,7 @@ import { renderAccountName, getTokenDetails, areAddressesEqual, + toChecksumAddress, } from '.'; import { mockHDKeyringAddress, @@ -534,6 +535,16 @@ describe('resemblesAddress', () => { expect(resemblesAddress(mockHDKeyringAddress)).toBeTruthy(); }); }); +describe('toChecksumAddress', () => { + it('should return the same address if it is invalid hex string', () => { + expect(toChecksumAddress('0x1')).toBe('0x1'); + expect(toChecksumAddress('0x123')).toBe('0x123'); + expect(toChecksumAddress('0x1234')).toBe('0x1234'); + }); + it('should throw an error if address is not a hex string', () => { + expect(() => toChecksumAddress('abc')).toThrow('Invalid hex address.'); + }); +}); describe('isSnapAccount,', () => { it('should return true if account is of type Snap Keyring', () => { expect(isSnapAccount(mockSnapAddress1)).toBeTruthy(); diff --git a/app/util/address/index.ts b/app/util/address/index.ts index 2b73ae0f3fb0..c3369e743664 100644 --- a/app/util/address/index.ts +++ b/app/util/address/index.ts @@ -1,10 +1,15 @@ import { - toChecksumAddress, isValidAddress, addHexPrefix, isValidChecksumAddress, isHexPrefixed, } from 'ethereumjs-util'; +import { + getChecksumAddress, + type Hex, + isHexString, + isStrictHexString, +} from '@metamask/utils'; import punycode from 'punycode/punycode'; import ExtendedKeyringTypes from '../../constants/keyringTypes'; import Engine from '../../core/Engine'; @@ -43,7 +48,6 @@ import type { NetworkState, } from '@metamask/network-controller'; import { KeyringTypes } from '@metamask/keyring-controller'; -import { type Hex, isHexString } from '@metamask/utils'; import PREINSTALLED_SNAPS from '../../lib/snaps/preinstalled-snaps'; import { EntropySourceId } from '@metamask/keyring-api'; @@ -478,9 +482,27 @@ export function resemblesAddress(address: string) { return address && address.length === 2 + 20 * 2; } +export function toChecksumAddress(address: string) { + try { + return getChecksumAddress(address as Hex); + } catch (error) { + // This is necessary for backward compatibility with the old behavior of + // `ethereumjs-util` which would return the original string if the address + // was invalid. This should happen only in tests which uses invalid addresses + // like 0x1, 0x2 etc. + if (error instanceof Error && error.message === 'Invalid hex address.') { + if (isStrictHexString(address)) { + return address as Hex; + } + throw new Error('Invalid hex address.'); + } + throw error; + } +} + export function safeToChecksumAddress(address?: string) { if (!address) return undefined; - return toChecksumAddress(address) as Hex; + return toChecksumAddress(address); } /** diff --git a/app/util/checkAddress.ts b/app/util/checkAddress.ts index 74c1d994a58b..bca965be1415 100644 --- a/app/util/checkAddress.ts +++ b/app/util/checkAddress.ts @@ -1,5 +1,5 @@ import type { AddressBookControllerState } from '@metamask/address-book-controller'; -import { toChecksumAddress } from 'ethereumjs-util'; +import { toChecksumAddress } from './address'; /** * Check whether the recipient of the given transaction is included in diff --git a/app/util/transactions/index.js b/app/util/transactions/index.js index fabea1e47e99..27d5092ce5f7 100644 --- a/app/util/transactions/index.js +++ b/app/util/transactions/index.js @@ -1,4 +1,4 @@ -import { addHexPrefix, toChecksumAddress } from 'ethereumjs-util'; +import { addHexPrefix } from 'ethereumjs-util'; import BN from 'bnjs4'; import { rawEncode, rawDecode } from 'ethereumjs-abi'; import BigNumber from 'bignumber.js'; @@ -17,7 +17,7 @@ import { import { swapsUtils } from '@metamask/swaps-controller'; import Engine from '../../core/Engine'; import I18n, { strings } from '../../../locales/i18n'; -import { safeToChecksumAddress } from '../address'; +import { safeToChecksumAddress, toChecksumAddress } from '../address'; import { balanceToFiatNumber, BNToHex,