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,