diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d9eedf316c..f4242412746 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -335,3 +335,16 @@ jobs: echo "All jobs passed step skipped. Block PR." exit 1 fi + + log-merge-group-failure: + name: Log merge group failure + # Only run this job if the merge group event fails, skip on forks + if: ${{ github.event_name == 'merge_group' && failure() && !github.event.repository.fork }} + needs: + - check-all-jobs-pass + uses: metamask/github-tools/.github/workflows/log-merge-group-failure.yml@6bbad335a01fce1a9ec1eabd9515542c225d46c0 + secrets: + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + GOOGLE_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} + SPREADSHEET_ID: ${{ secrets.GOOGLE_MERGE_QUEUE_SPREADSHEET_ID }} + SHEET_NAME: ${{ secrets.GOOGLE_MERGE_QUEUE_SHEET_NAME }} diff --git a/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.types.tsx b/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.types.tsx index de90049af66..b6ba4f94932 100644 --- a/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.types.tsx +++ b/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.types.tsx @@ -1,4 +1,4 @@ -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; interface SelectedAsset { isETH: boolean; diff --git a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx index 499a3eacb96..c672eb7dcde 100644 --- a/app/components/UI/AccountSelectorList/AccountSelectorList.tsx +++ b/app/components/UI/AccountSelectorList/AccountSelectorList.tsx @@ -11,7 +11,7 @@ import { selectInternalAccounts } from '../../../selectors/accountsController'; import Cell, { CellVariant, } from '../../../component-library/components/Cells/Cell'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { useStyles } from '../../../component-library/hooks'; import { TextColor } from '../../../component-library/components/Texts/Text'; import SensitiveText, { diff --git a/app/components/UI/SimulationDetails/formatAmount.ts b/app/components/UI/SimulationDetails/formatAmount.ts index 8aabfc4e553..8b5ebe3a8d4 100644 --- a/app/components/UI/SimulationDetails/formatAmount.ts +++ b/app/components/UI/SimulationDetails/formatAmount.ts @@ -1,7 +1,10 @@ import { BigNumber } from 'bignumber.js'; const MIN_AMOUNT = new BigNumber('0.000001'); -const PRECISION = 6; + +// The default precision for displaying currency values. +// It set to the number of decimal places in the minimum amount. +export const DEFAULT_PRECISION = new BigNumber(MIN_AMOUNT).decimalPlaces(); // The number of significant decimals places to show for amounts less than 1. const MAX_SIGNIFICANT_DECIMAL_PLACES = 3; @@ -12,11 +15,21 @@ export function formatAmountMaxPrecision( locale: string, num: number | BigNumber, ): string { - return new Intl.NumberFormat(locale, { - minimumSignificantDigits: 1, - }).format(new BigNumber(num.toString()).toNumber()); + const bigNumberValue = new BigNumber(num); + const numberOfDecimals = bigNumberValue.decimalPlaces(); + const formattedValue = bigNumberValue.toFixed(numberOfDecimals ?? 0); + + const [integerPart, fractionalPart] = formattedValue.split('.'); + const formattedIntegerPart = new Intl.NumberFormat(locale).format( + integerPart as unknown as number, + ); + + return fractionalPart + ? `${formattedIntegerPart}.${fractionalPart}` + : formattedIntegerPart; } + /** * Formats the a token amount with variable precision and significant * digits. @@ -65,7 +78,7 @@ export function formatAmount(locale: string, amount: BigNumber): string { return new Intl.NumberFormat(locale, { maximumSignificantDigits: MAX_SIGNIFICANT_DECIMAL_PLACES, } as Intl.NumberFormatOptions).format( - amount.decimalPlaces(PRECISION).toNumber(), + amount.dp(DEFAULT_PRECISION ?? 0).toNumber(), ); } @@ -73,7 +86,7 @@ export function formatAmount(locale: string, amount: BigNumber): string { // Cap the digits right of the decimal point: The more digits present // on the left side of the decimal point, the less decimal places // we show on the right side. - const digitsLeftOfDecimal = amount.abs().toFixed(0).length; + const digitsLeftOfDecimal = amount.abs().dp(0).toString().length; const maximumFractionDigits = Math.max( 0, MAX_SIGNIFICANT_DECIMAL_PLACES - digitsLeftOfDecimal + 1, @@ -81,5 +94,10 @@ export function formatAmount(locale: string, amount: BigNumber): string { return new Intl.NumberFormat(locale, { maximumFractionDigits, - } as Intl.NumberFormatOptions).format(amount.toNumber()); + } as Intl.NumberFormatOptions).format( + // string is valid parameter for format function + // for some reason it gives TS issue + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/format#number + amount.toFixed(maximumFractionDigits) as unknown as number, + ); } diff --git a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx index 250e675b460..97dcf6dfbcb 100644 --- a/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx +++ b/app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx @@ -52,7 +52,7 @@ import { TotalFiatBalancesCrossChains, useGetTotalFiatBalanceCrossChains, } from '../../../../hooks/useGetTotalFiatBalanceCrossChains'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { getChainIdsToPoll } from '../../../../../selectors/tokensController'; import AggregatedPercentageCrossChains from '../../../../../component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains'; diff --git a/app/components/Views/AccountActions/AccountActions.tsx b/app/components/Views/AccountActions/AccountActions.tsx index 720c9a53214..2841bf87722 100644 --- a/app/components/Views/AccountActions/AccountActions.tsx +++ b/app/components/Views/AccountActions/AccountActions.tsx @@ -11,7 +11,7 @@ import { useDispatch, useSelector } from 'react-redux'; import Share from 'react-native-share'; // External dependencies -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import BottomSheet, { BottomSheetRef, } from '../../../component-library/components/BottomSheets/BottomSheet'; @@ -397,7 +397,9 @@ const AccountActions = () => { actionTitle={strings('accounts.remove_hardware_account')} iconName={IconName.Close} onPress={showRemoveHWAlert} - testID={AccountActionsBottomSheetSelectorsIDs.REMOVE_HARDWARE_ACCOUNT} + testID={ + AccountActionsBottomSheetSelectorsIDs.REMOVE_HARDWARE_ACCOUNT + } /> )} { diff --git a/app/components/Views/AddAccountActions/AddAccountActions.tsx b/app/components/Views/AddAccountActions/AddAccountActions.tsx index 7cda0b6bdc0..80c2d8021c7 100644 --- a/app/components/Views/AddAccountActions/AddAccountActions.tsx +++ b/app/components/Views/AddAccountActions/AddAccountActions.tsx @@ -20,7 +20,7 @@ import { useMetrics } from '../../../components/hooks/useMetrics'; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import { CaipChainId } from '@metamask/utils'; -import { KeyringClient } from '@metamask/keyring-api'; +import { KeyringClient } from '@metamask/keyring-snap-client'; import { BitcoinWalletSnapSender } from '../../../core/SnapKeyring/BitcoinWalletSnap'; import { MultichainNetworks } from '../../../core/Multichain/constants'; import { useSelector } from 'react-redux'; diff --git a/app/components/Views/EditAccountName/EditAccountName.tsx b/app/components/Views/EditAccountName/EditAccountName.tsx index 0f69a2b7734..e1946deae14 100644 --- a/app/components/Views/EditAccountName/EditAccountName.tsx +++ b/app/components/Views/EditAccountName/EditAccountName.tsx @@ -10,7 +10,7 @@ import { useSelector } from 'react-redux'; import { SafeAreaView } from 'react-native'; // External dependencies -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import Text from '../../../component-library/components/Texts/Text/Text'; import { View } from 'react-native-animatable'; import { TextVariant } from '../../../component-library/components/Texts/Text'; diff --git a/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx b/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx index b3e921d6366..d06c2fb4d25 100644 --- a/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx +++ b/app/components/Views/RevealPrivateCredential/RevealPrivateCredential.tsx @@ -11,7 +11,7 @@ import { } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import QRCode from 'react-native-qrcode-svg'; import { RouteProp, ParamListBase } from '@react-navigation/native'; import ScrollableTabView, { diff --git a/app/components/Views/RevealPrivateCredential/index.test.tsx b/app/components/Views/RevealPrivateCredential/index.test.tsx index 6e9d90b7916..bbf92a7743f 100644 --- a/app/components/Views/RevealPrivateCredential/index.test.tsx +++ b/app/components/Views/RevealPrivateCredential/index.test.tsx @@ -2,11 +2,13 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react-native'; import configureMockStore from 'redux-mock-store'; import { Provider } from 'react-redux'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { backgroundState } from '../../../util/test/initial-root-state'; import { RevealPrivateCredential } from './'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { RevealSeedViewSelectorsIDs } from '../../../../e2e/selectors/Settings/SecurityAndPrivacy/RevealSeedView.selectors'; +import { EthAccountType, EthMethod, EthScopes } from '@metamask/keyring-api'; +import { KeyringTypes } from '@metamask/keyring-controller'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), @@ -147,24 +149,26 @@ describe('RevealPrivateCredential', () => { it('renders with a custom selectedAddress', async () => { const mockInternalAccount: InternalAccount = { - type: 'eip155:eoa', + type: EthAccountType.Eoa, id: 'unique-account-id-1', address: '0x1234567890123456789012345678901234567890', options: { someOption: 'optionValue', anotherOption: 42, }, + scopes: [EthScopes.Namespace], methods: [ - 'personal_sign', - 'eth_sign', - 'eth_signTransaction', - 'eth_sendTransaction', + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV1, + EthMethod.SignTypedDataV3, + EthMethod.SignTypedDataV4, ], metadata: { name: 'Test Account', importTime: Date.now(), keyring: { - type: 'HD Key Tree', + type: KeyringTypes.hd, }, nameLastUpdatedAt: Date.now(), snap: { diff --git a/app/components/Views/Snaps/KeyringSnapRemovalWarning/KeyringSnapRemovalWarning.tsx b/app/components/Views/Snaps/KeyringSnapRemovalWarning/KeyringSnapRemovalWarning.tsx index b2ec560b5dd..cd8b66c42a5 100644 --- a/app/components/Views/Snaps/KeyringSnapRemovalWarning/KeyringSnapRemovalWarning.tsx +++ b/app/components/Views/Snaps/KeyringSnapRemovalWarning/KeyringSnapRemovalWarning.tsx @@ -15,7 +15,7 @@ import BottomSheet, { import Text, { TextVariant, } from '../../../../component-library/components/Texts/Text'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import BannerAlert from '../../../../component-library/components/Banners/Banner/variants/BannerAlert'; import { BannerAlertSeverity } from '../../../../component-library/components/Banners/Banner'; import BottomSheetHeader from '../../../../component-library/components/BottomSheets/BottomSheetHeader'; @@ -170,56 +170,56 @@ export default function KeyringSnapRemovalWarning({ 'app_settings.snaps.snap_settings.remove_account_snap_warning.banner_title', )} /> - {showConfirmation ? ( - <> - - {`${strings( - 'app_settings.snaps.snap_settings.remove_account_snap_warning.remove_account_snap_alert_description_1', - )} `} - - {snap.manifest.proposedName} - - {` ${strings( - 'app_settings.snaps.snap_settings.remove_account_snap_warning.remove_account_snap_alert_description_2', - )}`} + {showConfirmation ? ( + <> + + {`${strings( + 'app_settings.snaps.snap_settings.remove_account_snap_warning.remove_account_snap_alert_description_1', + )} `} + + {snap.manifest.proposedName} - - {error && ( - - {strings( - 'app_settings.snaps.snap_settings.remove_account_snap_warning.remove_snap_error', - { - snapName: snap.manifest.proposedName, - }, - )} - - )} - - ) : ( - <> - + {` ${strings( + 'app_settings.snaps.snap_settings.remove_account_snap_warning.remove_account_snap_alert_description_2', + )}`} + + + {error && ( + {strings( - 'app_settings.snaps.snap_settings.remove_account_snap_warning.description', + 'app_settings.snaps.snap_settings.remove_account_snap_warning.remove_snap_error', + { + snapName: snap.manifest.proposedName, + }, )} - + )} + + ) : ( + <> + + {strings( + 'app_settings.snaps.snap_settings.remove_account_snap_warning.description', + )} + + - {accountListItems} - - - - )} - - + {accountListItems} + + + + )} + + ); } diff --git a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx index d56c1d0b200..e23a429e20f 100644 --- a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx +++ b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx @@ -31,7 +31,7 @@ import { selectPermissionControllerState } from '../../../../selectors/snaps/per import KeyringSnapRemovalWarning from '../KeyringSnapRemovalWarning/KeyringSnapRemovalWarning'; import { getAccountsBySnapId } from '../../../../core/SnapKeyring/utils/getAccountsBySnapId'; import { selectInternalAccounts } from '../../../../selectors/accountsController'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import Logger from '../../../../util/Logger'; interface SnapSettingsProps { snap: Snap; @@ -100,7 +100,6 @@ const SnapSettings = () => { setIsShowingSnapKeyringRemoveWarning(false); }, []); - const removeSnap = useCallback(async () => { const { SnapController } = Engine.context; await SnapController.removeSnap(snap.id); @@ -110,8 +109,11 @@ const SnapSettings = () => { for (const keyringAccount of keyringAccounts) { await Engine.removeAccount(keyringAccount.address); } - } catch(error) { - Logger.error(error as Error, 'SnapSettings: failed to remove snap accounts when calling Engine.removeAccount'); + } catch (error) { + Logger.error( + error as Error, + 'SnapSettings: failed to remove snap accounts when calling Engine.removeAccount', + ); } } navigation.goBack(); @@ -125,7 +127,6 @@ const SnapSettings = () => { } }, [isKeyringSnap, keyringAccounts.length, removeSnap]); - const handleRemoveSnapKeyring = useCallback(() => { try { setIsShowingSnapKeyringRemoveWarning(true); diff --git a/app/components/Views/Snaps/components/KeyringAccountListItem/KeyringAccountListItem.tsx b/app/components/Views/Snaps/components/KeyringAccountListItem/KeyringAccountListItem.tsx index f0935d131b8..7afe2fc148f 100644 --- a/app/components/Views/Snaps/components/KeyringAccountListItem/KeyringAccountListItem.tsx +++ b/app/components/Views/Snaps/components/KeyringAccountListItem/KeyringAccountListItem.tsx @@ -1,6 +1,6 @@ ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) import React, { useCallback } from 'react'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { toChecksumHexAddress } from '@metamask/controller-utils'; import ButtonIcon, { ButtonIconSizes, diff --git a/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/types.ts b/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/types.ts index beb9d1c0469..32ce99fcbdb 100644 --- a/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/types.ts +++ b/app/components/Views/confirmations/components/ApproveTransactionReview/AddNickname/types.ts @@ -1,6 +1,6 @@ import { AddressBookControllerState } from '@metamask/address-book-controller'; import { NetworkType } from '@metamask/controller-utils'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import type { NetworkState } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx index 44622075f16..dce63ce66a5 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -108,7 +108,7 @@ const SimulationValueDisplay: React.FC< tokenDetails as TokenDetailsERC20, ); - const tokenAmount = isNumberValue(value) && !tokenId ? calcTokenAmount(value, tokenDecimals) : null; + const tokenAmount = isNumberValue(value) && !tokenId ? calcTokenAmount(value as number | string, tokenDecimals) : null; const isValidTokenAmount = tokenAmount !== null && tokenAmount !== undefined && tokenAmount instanceof BigNumber; const fiatValue = isValidTokenAmount && exchangeRate && !tokenId diff --git a/app/components/hooks/useAccounts/useAccounts.ts b/app/components/hooks/useAccounts/useAccounts.ts index 3f52f3ab50a..4c354d5f612 100644 --- a/app/components/hooks/useAccounts/useAccounts.ts +++ b/app/components/hooks/useAccounts/useAccounts.ts @@ -31,7 +31,7 @@ import { UseAccounts, UseAccountsParams, } from './useAccounts.types'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { getChainIdsToPoll } from '../../../selectors/tokensController'; import { useGetFormattedTokensPerChain } from '../useGetFormattedTokensPerChain'; import { useGetTotalFiatBalanceCrossChains } from '../useGetTotalFiatBalanceCrossChains'; diff --git a/app/components/hooks/useAccounts/utils.ts b/app/components/hooks/useAccounts/utils.ts index 248f9d8d704..438c4166dc7 100644 --- a/app/components/hooks/useAccounts/utils.ts +++ b/app/components/hooks/useAccounts/utils.ts @@ -1,4 +1,4 @@ -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { getFormattedAddressFromInternalAccount } from '../../../core/Multichain/utils'; import { BigNumber } from 'ethers'; import { diff --git a/app/components/hooks/useGetFormattedTokensPerChain.test.ts b/app/components/hooks/useGetFormattedTokensPerChain.test.ts index cac00b8f731..6192d713c59 100644 --- a/app/components/hooks/useGetFormattedTokensPerChain.test.ts +++ b/app/components/hooks/useGetFormattedTokensPerChain.test.ts @@ -5,7 +5,7 @@ import { import { backgroundState } from '../../util/test/initial-root-state'; import { RootState } from '../../reducers'; import { useGetFormattedTokensPerChain } from './useGetFormattedTokensPerChain'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; const mockInitialState: DeepPartial = { settings: {}, diff --git a/app/components/hooks/useGetFormattedTokensPerChain.tsx b/app/components/hooks/useGetFormattedTokensPerChain.tsx index 526fd6874fe..c7c94472c72 100644 --- a/app/components/hooks/useGetFormattedTokensPerChain.tsx +++ b/app/components/hooks/useGetFormattedTokensPerChain.tsx @@ -16,7 +16,7 @@ import { selectCurrentCurrency, } from '../../selectors/currencyRateController'; import { MarketDataDetails, Token } from '@metamask/assets-controllers'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { isTestNet } from '../../util/networks'; import { selectShowFiatInTestnets } from '../../selectors/settings'; diff --git a/app/components/hooks/useGetTotalFiatBalanceCrossChains.test.ts b/app/components/hooks/useGetTotalFiatBalanceCrossChains.test.ts index a0a8383ca4a..8f014eaf5ac 100644 --- a/app/components/hooks/useGetTotalFiatBalanceCrossChains.test.ts +++ b/app/components/hooks/useGetTotalFiatBalanceCrossChains.test.ts @@ -5,7 +5,7 @@ import { import { backgroundState } from '../../util/test/initial-root-state'; import { RootState } from '../../reducers'; import { useGetTotalFiatBalanceCrossChains } from './useGetTotalFiatBalanceCrossChains'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; const mockInitialState: DeepPartial = { settings: {}, diff --git a/app/components/hooks/useGetTotalFiatBalanceCrossChains.tsx b/app/components/hooks/useGetTotalFiatBalanceCrossChains.tsx index 624b6571164..8275aacab63 100644 --- a/app/components/hooks/useGetTotalFiatBalanceCrossChains.tsx +++ b/app/components/hooks/useGetTotalFiatBalanceCrossChains.tsx @@ -11,7 +11,7 @@ import { selectCurrencyRates, selectCurrentCurrency, } from '../../selectors/currencyRateController'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { selectShowFiatInTestnets } from '../../selectors/settings'; import { isTestNet } from '../../util/networks'; diff --git a/app/core/Multichain/test/utils.test.ts b/app/core/Multichain/test/utils.test.ts index d4cb0d7ce49..0553a6ae20d 100644 --- a/app/core/Multichain/test/utils.test.ts +++ b/app/core/Multichain/test/utils.test.ts @@ -1,10 +1,12 @@ import { - InternalAccount, EthAccountType, BtcAccountType, EthMethod, BtcMethod, + EthScopes, + BtcScopes, } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { isEthAccount, isBtcAccount, @@ -28,6 +30,7 @@ const SOL_ADDRESSES = '7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV'; const mockEthEOAAccount: InternalAccount = { address: MOCK_ETH_ADDRESS, id: '1', + scopes: [EthScopes.Namespace], metadata: { name: 'Eth Account 1', importTime: 1684232000456, @@ -49,6 +52,7 @@ const mockEthEOAAccount: InternalAccount = { const mockEthERC4337Account: InternalAccount = { address: '0xC4966c0D659D99699BFD7EB54D8fafEE40e4a756', id: '1', + scopes: [EthScopes.Namespace], metadata: { name: 'Eth Account ERC4337 1', importTime: 1684232000456, @@ -70,6 +74,7 @@ const mockEthERC4337Account: InternalAccount = { const mockBTCAccount: InternalAccount = { address: MOCK_BTC_MAINNET_ADDRESS, id: '1', + scopes: [BtcScopes.Namespace], metadata: { name: 'Bitcoin Account', importTime: 1684232000456, diff --git a/app/core/Multichain/utils.ts b/app/core/Multichain/utils.ts index e963633786a..b853c207f97 100644 --- a/app/core/Multichain/utils.ts +++ b/app/core/Multichain/utils.ts @@ -1,6 +1,6 @@ import { toChecksumHexAddress } from '@metamask/controller-utils'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { - InternalAccount, EthAccountType, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) BtcAccountType, diff --git a/app/core/SnapKeyring/BitcoinWalletSnap.ts b/app/core/SnapKeyring/BitcoinWalletSnap.ts index e49890aacf7..b3da42ea99f 100644 --- a/app/core/SnapKeyring/BitcoinWalletSnap.ts +++ b/app/core/SnapKeyring/BitcoinWalletSnap.ts @@ -1,5 +1,5 @@ import { SnapId } from '@metamask/snaps-sdk'; -import { Sender } from '@metamask/keyring-api'; +import { Sender } from '@metamask/keyring-snap-client'; import { HandlerType } from '@metamask/snaps-utils'; import { Json, JsonRpcRequest } from '@metamask/utils'; // This dependency is still installed as part of the `package.json`, however diff --git a/app/core/SnapKeyring/SnapKeyring.test.ts b/app/core/SnapKeyring/SnapKeyring.test.ts index 0e8a181830b..594e7efa483 100644 --- a/app/core/SnapKeyring/SnapKeyring.test.ts +++ b/app/core/SnapKeyring/SnapKeyring.test.ts @@ -1,9 +1,6 @@ import { ControllerMessenger } from '@metamask/base-controller'; -import { - EthAccountType, - InternalAccount, - KeyringEvent, -} from '@metamask/keyring-api'; +import { EthAccountType, EthScopes, KeyringEvent } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { snapKeyringBuilder } from './SnapKeyring'; import { SnapKeyringBuilderAllowActions, @@ -28,6 +25,7 @@ const mockSetAccountName = jest.fn(); const mockFlowId = '123'; const address = '0x2a4d4b667D5f12C3F9Bf8F14a7B9f8D8d9b8c8fA'; const accountNameSuggestion = 'Suggested Account Name'; + const mockAccount = { type: EthAccountType.Eoa, id: '3afa663e-0600-4d93-868a-61c2e553013b', @@ -35,8 +33,9 @@ const mockAccount = { methods: [], options: {}, }; -const mockInternalAccount = { +const mockInternalAccount: InternalAccount = { ...mockAccount, + scopes: [EthScopes.Namespace], metadata: { snap: { enabled: true, diff --git a/app/selectors/accountsController.test.ts b/app/selectors/accountsController.test.ts index 5b6e9dd0231..92598446eef 100644 --- a/app/selectors/accountsController.test.ts +++ b/app/selectors/accountsController.test.ts @@ -1,7 +1,8 @@ import { AccountsControllerState } from '@metamask/accounts-controller'; import { captureException } from '@sentry/react-native'; import { Hex, isValidChecksumAddress } from '@metamask/utils'; -import { BtcAccountType, InternalAccount } from '@metamask/keyring-api'; +import { BtcAccountType } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import StorageWrapper from '../store/storage-wrapper'; import { selectSelectedInternalAccount, @@ -84,6 +85,7 @@ describe('Accounts Controller Selectors', () => { address: '0xc4966c0d659d99699bfd7eb54d8fafee40e4a756', id: expectedUuid2, options: {}, + scopes: ['eip155'], metadata: { name: 'Account 2', importTime: 1684232000456, diff --git a/app/selectors/accountsController.ts b/app/selectors/accountsController.ts index 3dbf2580f7b..f6811b6b4d0 100644 --- a/app/selectors/accountsController.ts +++ b/app/selectors/accountsController.ts @@ -4,7 +4,8 @@ import { createSelector } from 'reselect'; import { RootState } from '../reducers'; import { createDeepEqualSelector } from './util'; import { selectFlattenedKeyringAccounts } from './keyringController'; -import { EthMethod, InternalAccount } from '@metamask/keyring-api'; +import { EthMethod } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { getFormattedAddressFromInternalAccount, ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) diff --git a/app/store/migrations/036.test.ts b/app/store/migrations/036.test.ts index 95f6a354b94..ebf5f797375 100644 --- a/app/store/migrations/036.test.ts +++ b/app/store/migrations/036.test.ts @@ -1,7 +1,9 @@ -import { EthMethod, InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType, EthMethod, EthScopes } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import migrate, { Identity } from './036'; import { captureException } from '@sentry/react-native'; import { getUUIDFromAddressOfNormalAccount } from '@metamask/accounts-controller'; +import { KeyringTypes } from '@metamask/keyring-controller'; jest.mock('@sentry/react-native', () => ({ captureException: jest.fn(), @@ -48,12 +50,13 @@ function expectedInternalAccount( ): InternalAccount { return { address, + scopes: [EthScopes.Namespace], id: getUUIDFromAddressOfNormalAccount(address), metadata: { name: nickname, importTime: Date.now(), keyring: { - type: 'HD Key Tree', + type: KeyringTypes.hd, }, lastSelected: lastSelected ? expect.any(Number) : undefined, }, @@ -65,7 +68,7 @@ function expectedInternalAccount( EthMethod.SignTypedDataV3, EthMethod.SignTypedDataV4, ], - type: 'eip155:eoa', + type: EthAccountType.Eoa, }; } diff --git a/app/store/migrations/036.ts b/app/store/migrations/036.ts index 84d5b6a521b..b73aeae3171 100644 --- a/app/store/migrations/036.ts +++ b/app/store/migrations/036.ts @@ -1,4 +1,5 @@ -import { EthAccountType, InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType, EthScopes } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { isObject, hasProperty } from '@metamask/utils'; import { captureException } from '@sentry/react-native'; import { getUUIDFromAddressOfNormalAccount } from '@metamask/accounts-controller'; @@ -112,6 +113,7 @@ function createInternalAccountsForAccountsController( accounts[expectedId] = { address: identity.address, + scopes: [EthScopes.Namespace], id: expectedId, options: {}, metadata: { diff --git a/app/store/migrations/042.ts b/app/store/migrations/042.ts index 86491ef8050..62ab512881e 100644 --- a/app/store/migrations/042.ts +++ b/app/store/migrations/042.ts @@ -5,7 +5,7 @@ import { AccountsControllerState, getUUIDFromAddressOfNormalAccount, } from '@metamask/accounts-controller'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { isDefaultAccountName } from '../../util/ENSUtils'; import { ETH_EOA_METHODS } from '../../constants/eth-methods'; diff --git a/app/store/migrations/066.test.ts b/app/store/migrations/066.test.ts new file mode 100644 index 00000000000..970593be3ea --- /dev/null +++ b/app/store/migrations/066.test.ts @@ -0,0 +1,403 @@ +import { + BtcScopes, + EthScopes, + SolScopes, + EthMethod, +} from '@metamask/keyring-api'; +import { AccountsControllerState } from '@metamask/accounts-controller'; +import { captureException } from '@sentry/react-native'; +import migration from './066'; + +jest.mock('../../util/Logger'); +jest.mock('@sentry/react-native', () => ({ + captureException: jest.fn(), +})); + +const mockedCaptureException = jest.mocked(captureException); + +interface StateType { + engine: { + backgroundState: { + AccountsController: AccountsControllerState; + }; + }; +} + +describe('migration #66', () => { + const MOCK_INVALID_STATE = { + someKey: 'someValue', + }; + + const MOCK_EMPTY_STATE: StateType = { + engine: { + backgroundState: { + AccountsController: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + }, + }, + }; + + const MOCK_STATE_WITH_ACCOUNTS: StateType = { + engine: { + backgroundState: { + AccountsController: { + internalAccounts: { + selectedAccount: 'evm-1', + accounts: { + 'evm-1': { + id: 'evm-1', + type: 'eip155:eoa', + address: '0x123', + options: {}, + metadata: { + name: 'Account 1', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + scopes: [], + }, + 'evm-2': { + id: 'evm-2', + type: 'eip155:erc4337', + address: '0x456', + options: {}, + metadata: { + name: 'Account 2', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + scopes: [], + }, + 'btc-1': { + id: 'btc-1', + type: 'bip122:p2wpkh', + address: 'bc1abc', + options: {}, + metadata: { + name: 'BTC Account', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [], + scopes: [], + }, + 'sol-1': { + id: 'sol-1', + type: 'solana:data-account', + address: 'solana123', + options: {}, + metadata: { + name: 'Solana Account', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [], + scopes: [], + }, + }, + }, + }, + }, + }, + }; + + const MOCK_STATE_WITH_EXISTING_SCOPES: StateType = { + engine: { + backgroundState: { + AccountsController: { + internalAccounts: { + selectedAccount: 'evm-1', + accounts: { + 'evm-1': { + id: 'evm-1', + type: 'eip155:eoa', + address: '0x123', + options: {}, + metadata: { + name: 'Account 1', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + scopes: [EthScopes.Namespace], + }, + }, + }, + }, + }, + }, + }; + + it('captures exception for invalid state structure', () => { + const invalidState = { + engine: { + backgroundState: { + AccountsController: 'not an object', // Invalid type + }, + }, + }; + + const result = migration(invalidState); + + expect(captureException).toHaveBeenCalledWith( + expect.objectContaining({ + message: expect.stringContaining( + 'Invalid state structure for AccountsController', + ), + }), + ); + expect(result).toBe(invalidState); + }); + + it('handles completely missing AccountsController', () => { + const stateWithoutAccounts = { + engine: { + backgroundState: {}, + }, + }; + + const result = migration(stateWithoutAccounts); + + expect(captureException).toHaveBeenCalledWith( + expect.objectContaining({ + message: expect.stringContaining( + 'Invalid state structure for AccountsController', + ), + }), + ); + expect(result).toBe(stateWithoutAccounts); + }); + + it('handles unexpected errors', () => { + const malformedState = null; + + const result = migration(malformedState); + + expect(captureException).toHaveBeenCalled(); + expect(result).toBe(malformedState); + }); + it('returns state if not valid', () => { + const result = migration(MOCK_INVALID_STATE); + expect(result).toEqual(MOCK_INVALID_STATE); + }); + + it('returns state if empty accounts', () => { + const result = migration(MOCK_EMPTY_STATE); + expect(result).toEqual(MOCK_EMPTY_STATE); + }); + + it('preserves accounts that have valid scopes', () => { + const stateCopy = JSON.parse( + JSON.stringify(MOCK_STATE_WITH_EXISTING_SCOPES), + ); + const result = migration(stateCopy) as StateType; + expect(result).toEqual(MOCK_STATE_WITH_EXISTING_SCOPES); + }); + + it('adds correct scopes for all account types', () => { + const stateCopy = JSON.parse(JSON.stringify(MOCK_STATE_WITH_ACCOUNTS)); + const result = migration(stateCopy) as StateType; + const accounts = + result.engine.backgroundState.AccountsController.internalAccounts + .accounts; + + // Check EVM EOA account + expect(accounts['evm-1']?.scopes).toEqual([EthScopes.Namespace]); + + // Check EVM ERC4337 account + expect(accounts['evm-2']?.scopes).toEqual([EthScopes.Namespace]); + + // Check BTC account + expect(accounts['btc-1']?.scopes).toEqual([BtcScopes.Mainnet]); + + // Check Solana account + expect(accounts['sol-1']?.scopes).toEqual([ + SolScopes.Mainnet, + SolScopes.Testnet, + SolScopes.Devnet, + ]); + }); + + it('handles malformed account objects gracefully', () => { + const malformedState: StateType = { + engine: { + backgroundState: { + AccountsController: { + internalAccounts: { + selectedAccount: 'valid-1', + accounts: { + 'valid-1': { + id: 'valid-1', + type: 'eip155:eoa', + address: '0x123', + options: {}, + metadata: { + name: 'Account 1', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + scopes: [], + }, + }, + }, + }, + }, + }, + }; + + const result = migration(malformedState) as StateType; + const accounts = + result.engine.backgroundState.AccountsController.internalAccounts + .accounts; + + // Should still process valid accounts + expect(accounts['valid-1']?.scopes).toEqual([EthScopes.Namespace]); + }); + + it('handles invalid scopes property gracefully', () => { + const stateWithInvalidScopes: StateType = { + engine: { + backgroundState: { + AccountsController: { + internalAccounts: { + selectedAccount: 'invalid-1', + accounts: { + 'invalid-1': { + id: 'invalid-1', + type: 'eip155:eoa', + address: '0x123', + options: {}, + metadata: { + name: 'Account 1', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + // @ts-expect-error Testing invalid scope type + scopes: null, + }, + 'invalid-2': { + id: 'invalid-2', + type: 'eip155:eoa', + address: '0x456', + options: {}, + metadata: { + name: 'Account 2', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + scopes: [], + }, + 'invalid-3': { + id: 'invalid-3', + type: 'eip155:eoa', + address: '0x789', + options: {}, + metadata: { + name: 'Account 3', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [ + EthMethod.PersonalSign, + EthMethod.SignTransaction, + EthMethod.SignTypedDataV4, + ], + // @ts-expect-error Testing invalid scope type + scopes: undefined, + }, + }, + }, + }, + }, + }, + }; + + const result = migration(stateWithInvalidScopes) as StateType; + const accounts = + result.engine.backgroundState.AccountsController.internalAccounts + .accounts; + + // Should fix accounts with invalid scopes + expect(accounts['invalid-1']?.scopes).toEqual([EthScopes.Namespace]); + expect(accounts['invalid-2']?.scopes).toEqual([EthScopes.Namespace]); + expect(accounts['invalid-3']?.scopes).toEqual([EthScopes.Namespace]); + }); + + it('logs unknown account types to Sentry', () => { + const stateWithUnknownType: StateType = { + engine: { + backgroundState: { + AccountsController: { + internalAccounts: { + selectedAccount: 'unknown-1', + accounts: { + 'unknown-1': { + id: 'unknown-1', + // @ts-expect-error Testing unknown account type + type: 'unknown-type', + address: '0x123', + options: {}, + metadata: { + name: 'Unknown Account', + keyring: { type: 'HD Key Tree' }, + importTime: Date.now(), + }, + methods: [], + scopes: [], + }, + }, + }, + }, + }, + }, + }; + + const result = migration(stateWithUnknownType) as StateType; + const accounts = + result.engine.backgroundState.AccountsController.internalAccounts + .accounts; + + // Verify scopes are set to default EVM namespace + expect(accounts['unknown-1']?.scopes).toEqual([EthScopes.Namespace]); + + // Verify Sentry exception was captured + expect(mockedCaptureException).toHaveBeenCalledWith( + new Error( + 'Migration 66: Unknown account type unknown-type, defaulting to EVM namespace', + ), + ); + }); +}); diff --git a/app/store/migrations/066.ts b/app/store/migrations/066.ts new file mode 100644 index 00000000000..210a0db07f4 --- /dev/null +++ b/app/store/migrations/066.ts @@ -0,0 +1,105 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { ensureValidState } from './util'; +import Logger from '../../util/Logger'; +import { + BtcAccountType, + BtcScopes, + EthAccountType, + EthScopes, + SolAccountType, + SolScopes, +} from '@metamask/keyring-api'; +import { captureException } from '@sentry/react-native'; + +const migrationVersion = 66; + +function getScopesForAccountType(accountType: string): string[] { + switch (accountType) { + case EthAccountType.Eoa: + case EthAccountType.Erc4337: + return [EthScopes.Namespace]; + case BtcAccountType.P2wpkh: + // Default to mainnet scope if address is missing or invalid + return [BtcScopes.Mainnet]; + case SolAccountType.DataAccount: + return [SolScopes.Mainnet, SolScopes.Testnet, SolScopes.Devnet]; + default: + // Default to EVM namespace for unknown account types + captureException( + new Error( + `Migration ${migrationVersion}: Unknown account type ${accountType}, defaulting to EVM namespace`, + ), + ); + return [EthScopes.Namespace]; + } +} + +/** + * Migration for adding scopes to accounts in the AccountsController. + * Each account type gets its appropriate scopes: + * - EVM EOA: [EthScopes.Namespace] + * - EVM ERC4337: [EthScopes.Namespace] + * - BTC P2WPKH: [BtcScopes.Mainnet] or [BtcScopes.Testnet] based on address + * - Solana: [SolScopes.Mainnet, SolScopes.Testnet, SolScopes.Devnet] + * + * @param state - The state to migrate + * @returns The migrated state + */ +export default function migrate(state: unknown) { + if (!ensureValidState(state, migrationVersion)) { + return state; + } + + if ( + !hasProperty(state.engine.backgroundState, 'AccountsController') || + !isObject(state.engine.backgroundState.AccountsController) || + !hasProperty( + state.engine.backgroundState.AccountsController, + 'internalAccounts', + ) || + !isObject( + state.engine.backgroundState.AccountsController.internalAccounts, + ) || + !hasProperty( + state.engine.backgroundState.AccountsController.internalAccounts, + 'accounts', + ) || + !isObject( + state.engine.backgroundState.AccountsController.internalAccounts.accounts, + ) + ) { + captureException( + new Error( + `Migration ${migrationVersion}: Invalid state structure for AccountsController`, + ), + ); + return state; + } + + const accounts = + state.engine.backgroundState.AccountsController.internalAccounts.accounts; + + for (const account of Object.values(accounts)) { + if (!isObject(account) || !hasProperty(account, 'type')) { + continue; + } + + // Skip if account already has valid scopes + if ( + hasProperty(account, 'scopes') && + Array.isArray(account.scopes) && + account.scopes.length > 0 && + account.scopes.every((scope) => typeof scope === 'string') + ) { + continue; + } + + Logger.log( + `Migration ${migrationVersion}: Adding scopes for account type ${account.type}`, + ); + + account.scopes = getScopesForAccountType(account.type as string); + } + + return state; +} diff --git a/app/store/validateMigration/accountsController.test.ts b/app/store/validateMigration/accountsController.test.ts index 63a636f8243..1ea53201d9a 100644 --- a/app/store/validateMigration/accountsController.test.ts +++ b/app/store/validateMigration/accountsController.test.ts @@ -4,6 +4,7 @@ import { RootState } from '../../reducers'; import { AccountsControllerState } from '@metamask/accounts-controller'; import { EngineState } from '../../core/Engine/types'; import { Json } from '@metamask/utils'; +import { EthScopes } from '@metamask/keyring-api'; describe('validateAccountsController', () => { const createMockState = ( @@ -25,6 +26,7 @@ describe('validateAccountsController', () => { id: 'account-1', address: '0x123', type: 'eip155:eoa', + scopes: [EthScopes.Namespace], options: {} as Record, methods: [], metadata: { @@ -87,6 +89,7 @@ describe('validateAccountsController', () => { id: 'account-1', address: '0x123', type: 'eip155:eoa', + scopes: [EthScopes.Namespace], options: {} as Record, methods: [], metadata: { @@ -117,6 +120,7 @@ describe('validateAccountsController', () => { id: 'account-1', address: '0x123', type: 'eip155:eoa', + scopes: [EthScopes.Namespace], options: {} as Record, methods: [], metadata: { diff --git a/app/util/address/index.ts b/app/util/address/index.ts index a961ed6cda5..d4019e5f459 100644 --- a/app/util/address/index.ts +++ b/app/util/address/index.ts @@ -33,7 +33,7 @@ import { selectChainId } from '../../selectors/networkController'; import { store } from '../../store'; import { regex } from '../../../app/util/regex'; import Logger from '../../../app/util/Logger'; -import { InternalAccount } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { AddressBookControllerState } from '@metamask/address-book-controller'; import { NetworkType, toChecksumHexAddress } from '@metamask/controller-utils'; import { NetworkClientId, NetworkState } from '@metamask/network-controller'; diff --git a/app/util/notifications/hooks/types.ts b/app/util/notifications/hooks/types.ts index 2e0886d5af1..1e5d193c9e9 100644 --- a/app/util/notifications/hooks/types.ts +++ b/app/util/notifications/hooks/types.ts @@ -1,4 +1,4 @@ -import type { InternalAccount } from '@metamask/keyring-api'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; import { KeyringTypes } from '@metamask/keyring-controller'; import type { Notification } from '../../../util/notifications/types/notification'; diff --git a/app/util/number/index.js b/app/util/number/index.js index 05e8cbececd..f84cc95c189 100644 --- a/app/util/number/index.js +++ b/app/util/number/index.js @@ -16,6 +16,9 @@ import { isZero } from '../lodash'; import { regex } from '../regex'; export { BNToHex }; +const MAX_DECIMALS_FOR_TOKENS = 36; +BigNumber.config({ DECIMAL_PLACES: MAX_DECIMALS_FOR_TOKENS }); + // Big Number Constants const BIG_NUMBER_WEI_MULTIPLIER = new BigNumber('1000000000000000000'); const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber('1000000000'); diff --git a/app/util/test/accountsControllerTestUtils.ts b/app/util/test/accountsControllerTestUtils.ts index 3aba07841fb..d4cfe766244 100644 --- a/app/util/test/accountsControllerTestUtils.ts +++ b/app/util/test/accountsControllerTestUtils.ts @@ -1,10 +1,15 @@ import { v4 as uuidV4 } from 'uuid'; import { EthAccountType, + BtcAccountType, + SolAccountType, EthMethod, - InternalAccount, + EthScopes, + BtcScopes, + SolScopes, KeyringAccountType, } from '@metamask/keyring-api'; +import { InternalAccount } from '@metamask/keyring-internal-api'; import { AccountsControllerState } from '@metamask/accounts-controller'; import { KeyringTypes } from '@metamask/keyring-controller'; import { @@ -24,6 +29,33 @@ export function createMockUuidFromAddress(address: string): string { }); } +/** + * Maps account types to their corresponding scopes + * @param accountType - The type of account (ETH, BTC, or Solana) + * @returns Array of scopes corresponding to the account type + */ +function getAccountTypeScopes(accountType: KeyringAccountType): string[] { + // Define scope mappings + const scopeMappings = { + // Ethereum account types + [EthAccountType.Eoa]: [EthScopes.Namespace], + [EthAccountType.Erc4337]: [EthScopes.Namespace], + + // Bitcoin account types + [BtcAccountType.P2wpkh]: [BtcScopes.Namespace], + + // Solana account types + [SolAccountType.DataAccount]: [SolScopes.Namespace], + }; + + const scopes = scopeMappings[accountType]; + if (!scopes) { + throw new Error(`Unsupported account type: ${accountType}`); + } + + return scopes; +} + export function createMockInternalAccount( address: string, nickname: string, @@ -62,6 +94,7 @@ export function createMockInternalAccount( EthMethod.SignTypedDataV4, ], type: accountType, + scopes: getAccountTypeScopes(accountType), }; } @@ -92,7 +125,8 @@ export function createMockSnapInternalAccount( EthMethod.SignTypedDataV3, EthMethod.SignTypedDataV4, ], - type: 'eip155:eoa', + type: EthAccountType.Eoa, + scopes: [EthScopes.Namespace], }; } diff --git a/app/util/transactions/index.js b/app/util/transactions/index.js index 8315154bf9a..eadb29d6e21 100644 --- a/app/util/transactions/index.js +++ b/app/util/transactions/index.js @@ -1428,9 +1428,14 @@ export function validateTransactionActionBalance(transaction, rate, accounts) { } } +/** + * @param {number|string|BigNumber} value + * @param {number=} decimals + * @returns {BigNumber} + */ export function calcTokenAmount(value, decimals) { - const multiplier = Math.pow(10, Number(decimals || 0)); - return new BigNumber(String(value)).div(multiplier); + const divisor = new BigNumber(10).pow(decimals ?? 0); + return new BigNumber(String(value)).div(divisor); } export function calcTokenValue(value, decimals) { diff --git a/app/util/transactions/index.test.ts b/app/util/transactions/index.test.ts index f2fcc535372..aa3d10495e0 100644 --- a/app/util/transactions/index.test.ts +++ b/app/util/transactions/index.test.ts @@ -11,6 +11,7 @@ import { UINT256_BN_MAX_VALUE } from '../../constants/transaction'; import { NEGATIVE_TOKEN_DECIMALS } from '../../constants/error'; import { generateTransferData, + calcTokenAmount, decodeApproveData, decodeTransferData, getMethodData, @@ -42,6 +43,7 @@ import Engine from '../../core/Engine'; import { strings } from '../../../locales/i18n'; import { TransactionType } from '@metamask/transaction-controller'; import { Provider } from '@metamask/network-controller'; +import BigNumber from 'bignumber.js'; jest.mock('@metamask/controller-utils', () => ({ ...jest.requireActual('@metamask/controller-utils'), @@ -106,6 +108,52 @@ describe('Transactions utils :: generateTransferData', () => { }); }); +describe('Transactions utils :: calcTokenAmount', () => { + it.each([ + // number values + [0, 5, '0'], + [123456, undefined, '123456'], + [123456, 5, '1.23456'], + [123456, 6, '0.123456'], + // Do not delete the following test. Testing decimal = 36 is important because it has broken + // BigNumber#div in the past when the value that was passed into it was not a BigNumber. + [123456, 36, '1.23456e-31'], + [3000123456789678, 6, '3000123456.789678'], + // eslint-disable-next-line @typescript-eslint/no-loss-of-precision + [3000123456789123456789123456789, 3, '3.0001234567891233e+27'], // expected precision lost + // eslint-disable-next-line @typescript-eslint/no-loss-of-precision + [3000123456789123456789123456789, 6, '3.0001234567891233e+24'], // expected precision lost + // string values + ['0', 5, '0'], + ['123456', undefined, '123456'], + ['123456', 5, '1.23456'], + ['123456', 6, '0.123456'], + ['3000123456789678', 6, '3000123456.789678'], + [ + '3000123456789123456789123456789', + 3, + '3.000123456789123456789123456789e+27', + ], + [ + '3000123456789123456789123456789', + 6, + '3.000123456789123456789123456789e+24', + ], + // BigNumber values + [new BigNumber('3000123456789678'), 6, '3000123456.789678'], + [ + new BigNumber('3000123456789123456789123456789'), + 6, + '3.000123456789123456789123456789e+24', + ], + ])( + 'returns the value %s divided by 10^%s = %s', + (value, decimals, expected) => { + expect(calcTokenAmount(value, decimals).toString()).toBe(expected); + }, + ); +}); + describe('Transactions utils :: decodeTransferData', () => { it('decodeTransferData transfer', () => { const [address, amount] = decodeTransferData( diff --git a/package.json b/package.json index fb288afa5cb..5230866d3d8 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "prestorybook": "rnstl", "deduplicate": "yarn yarn-deduplicate && yarn install", "patch:tx": "./scripts/patch-transaction-controller.sh", - "patch:assets": "./scripts/patch-assets-controllers.sh", "patch:approval": "./scripts/patch-approval-controller.sh", "storybook-generate": "sb-rn-get-stories", "storybook-watch": "sb-rn-watcher", @@ -112,7 +111,7 @@ "react-native-level-fs/**/bl": "^1.2.3", "react-native-level-fs/levelup/semver": "^5.7.2", "@metamask/react-native-payments/validator": "^13.7.0", - "**/@metamask/utils": "^10.0.0", + "**/@metamask/utils": "^11.0.1", "**/minimist": "1.2.6", "d3-color": "3.1.0", "tough-cookie": "4.1.3", @@ -135,7 +134,7 @@ "send": "0.19.0", "ethereumjs-util/**/secp256k1": "3.8.1", "**/secp256k1": "4.0.4", - "**/@metamask/rpc-errors": "7.0.1", + "**/@metamask/rpc-errors": "7.0.2", "**/@expo/image-utils/semver": "7.5.2", "base58-js": "1.0.0", "bech32": "2.0.0", @@ -150,11 +149,11 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@keystonehq/ur-decoder": "^0.12.2", "@ledgerhq/react-native-hw-transport-ble": "^6.33.2", - "@metamask/accounts-controller": "^20.0.1", + "@metamask/accounts-controller": "^21.0.0", "@metamask/address-book-controller": "^6.0.1", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^45.1.1", - "@metamask/base-controller": "^7.0.1", + "@metamask/assets-controllers": "^46.0.0", + "@metamask/base-controller": "^7.1.1", "@metamask/bitcoin-wallet-snap": "^0.8.2", "@metamask/composable-controller": "^10.0.0", "@metamask/controller-utils": "^11.3.0", @@ -165,17 +164,19 @@ "@metamask/eth-ledger-bridge-keyring": "^8.0.0", "@metamask/eth-query": "^4.0.0", "@metamask/eth-sig-util": "^8.0.0", - "@metamask/eth-snap-keyring": "^5.0.1", + "@metamask/eth-snap-keyring": "^7.0.0", "@metamask/etherscan-link": "^2.0.0", "@metamask/ethjs-contract": "^0.4.1", "@metamask/ethjs-query": "^0.7.1", "@metamask/ethjs-unit": "^0.3.0", "@metamask/gas-fee-controller": "^22.0.2", - "@metamask/json-rpc-engine": "^10.0.0", - "@metamask/json-rpc-middleware-stream": "^8.0.2", + "@metamask/json-rpc-engine": "^10.0.2", + "@metamask/json-rpc-middleware-stream": "^8.0.6", "@metamask/key-tree": "^9.0.0", - "@metamask/keyring-api": "^10.1.0", + "@metamask/keyring-api": "^13.0.0", "@metamask/keyring-controller": "^19.0.1", + "@metamask/keyring-internal-api": "^2.0.0", + "@metamask/keyring-snap-client": "^2.0.0", "@metamask/logging-controller": "^6.0.1", "@metamask/message-signing-snap": "^0.3.3", "@metamask/network-controller": "^22.1.0", @@ -192,7 +193,7 @@ "@metamask/react-native-search-api": "1.0.1", "@metamask/react-native-webview": "^14.0.4", "@metamask/remote-feature-flag-controller": "^1.0.0", - "@metamask/rpc-errors": "^7.0.1", + "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.0", "@metamask/sdk-communication-layer": "0.29.0-wallet", "@metamask/selected-network-controller": "^19.0.0", @@ -208,7 +209,7 @@ "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^12.0.0", "@metamask/transaction-controller": "^42.0.0", - "@metamask/utils": "^10.0.1", + "@metamask/utils": "^11.0.1", "@ngraveio/bc-ur": "^1.1.6", "@notifee/react-native": "^9.0.0", "@react-native-async-storage/async-storage": "^1.23.1", @@ -390,7 +391,7 @@ "@metamask/eslint-plugin-design-tokens": "^1.0.0", "@metamask/mobile-provider": "^3.0.0", "@metamask/object-multiplex": "^1.1.0", - "@metamask/providers": "^18.1.0", + "@metamask/providers": "^18.3.1", "@metamask/test-dapp": "^8.9.0", "@octokit/rest": "^21.0.0", "@open-rpc/mock-server": "^1.7.5", diff --git a/patches/@metamask+assets-controllers+45.1.1.patch b/patches/@metamask+assets-controllers+46.0.0.patch similarity index 60% rename from patches/@metamask+assets-controllers+45.1.1.patch rename to patches/@metamask+assets-controllers+46.0.0.patch index 106e984e11c..7bff7d0e905 100644 --- a/patches/@metamask+assets-controllers+45.1.1.patch +++ b/patches/@metamask+assets-controllers+46.0.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.cjs b/node_modules/@metamask/assets-controllers/dist/NftController.cjs -index 6ccbe9c..49270d6 100644 +index 6ccbe9c..417a55e 100644 --- a/node_modules/@metamask/assets-controllers/dist/NftController.cjs +++ b/node_modules/@metamask/assets-controllers/dist/NftController.cjs @@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( @@ -129,13 +129,13 @@ index 6ccbe9c..49270d6 100644 ]); + if (blockchainMetadata?.error && nftApiMetadata?.error) { + return { -+ image: null, -+ name: null, -+ description: null, -+ standard: blockchainMetadata.standard ?? null, -+ favorite: false, -+ tokenURI: blockchainMetadata.tokenURI ?? null, -+ error: 'Both import failed', ++ image: null, ++ name: null, ++ description: null, ++ standard: blockchainMetadata.standard ?? null, ++ favorite: false, ++ tokenURI: blockchainMetadata.tokenURI ?? null, ++ error: 'Both import failed', + }; + } return { @@ -145,7 +145,7 @@ index 6ccbe9c..49270d6 100644 tokenId: tokenId.toString(), standard: nftMetadata.standard, source, -+ tokenURI: nftMetadata.tokenURI, ++ tokenURI: nftMetadata.tokenURI }); } } @@ -161,109 +161,3 @@ index a34725f..21e9d20 100644 collection?: Collection; address?: string; attributes?: Attributes[]; -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs -index c5aa814..83c0664 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs -@@ -220,50 +220,57 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_ - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.messagingSystem.subscribe('KeyringController:unlock', async () => { -- __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, true, "f"); -- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this); -- }); -- this.messagingSystem.subscribe('KeyringController:lock', () => { -- __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, false, "f"); -- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_stopPolling).call(this); -- }); -- this.messagingSystem.subscribe('TokenListController:stateChange', -- // TODO: Either fix this lint violation or explain why it's necessary to ignore. -- // eslint-disable-next-line @typescript-eslint/no-misused-promises -- async ({ tokensChainsCache }) => { -- const isEqualValues = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_compareTokensChainsCache).call(this, tokensChainsCache, __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")); -- if (!isEqualValues) { -- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this); -- } -- }); -- this.messagingSystem.subscribe('PreferencesController:stateChange', -- // TODO: Either fix this lint violation or explain why it's necessary to ignore. -- // eslint-disable-next-line @typescript-eslint/no-misused-promises -- async ({ useTokenDetection }) => { -- const selectedAccount = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this); -- const isDetectionChangedFromPreferences = __classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") !== useTokenDetection; -- __classPrivateFieldSet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, useTokenDetection, "f"); -- if (isDetectionChangedFromPreferences) { -- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { -- selectedAddress: selectedAccount.address, -- }); -- } -- }); -- this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange', -- // TODO: Either fix this lint violation or explain why it's necessary to ignore. -- // eslint-disable-next-line @typescript-eslint/no-misused-promises -- async (selectedAccount) => { -- const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); -- const chainIds = Object.keys(networkConfigurationsByChainId); -- const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id; -- if (isSelectedAccountIdChanged) { -- __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f"); -- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { -- selectedAddress: selectedAccount.address, -- chainIds, -- }); -- } -- }); -+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); -+ const chainIds = Object.keys(networkConfigurationsByChainId); -+ __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, true, "f"); -+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { chainIds }); -+ }); -+ this.messagingSystem.subscribe('KeyringController:lock', () => { -+ __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, false, "f"); -+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_stopPolling).call(this); -+ }); -+ this.messagingSystem.subscribe('TokenListController:stateChange', -+ // TODO: Either fix this lint violation or explain why it's necessary to ignore. -+ // eslint-disable-next-line @typescript-eslint/no-misused-promises -+ async ({ tokensChainsCache }) => { -+ const isEqualValues = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_compareTokensChainsCache).call(this, tokensChainsCache, __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")); -+ if (!isEqualValues) { -+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); -+ const chainIds = Object.keys(networkConfigurationsByChainId); -+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { chainIds }); -+ } -+ }); -+ this.messagingSystem.subscribe('PreferencesController:stateChange', -+ // TODO: Either fix this lint violation or explain why it's necessary to ignore. -+ // eslint-disable-next-line @typescript-eslint/no-misused-promises -+ async ({ useTokenDetection }) => { -+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); -+ const selectedAccount = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this); -+ const isDetectionChangedFromPreferences = __classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") !== useTokenDetection; -+ __classPrivateFieldSet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, useTokenDetection, "f"); -+ const chainIds = Object.keys(networkConfigurationsByChainId); -+ if (isDetectionChangedFromPreferences) { -+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { -+ selectedAddress: selectedAccount.address, -+ chainIds, -+ }); -+ } -+ }); -+ this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange', -+ // TODO: Either fix this lint violation or explain why it's necessary to ignore. -+ // eslint-disable-next-line @typescript-eslint/no-misused-promises -+ async (selectedAccount) => { -+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); -+ const chainIds = Object.keys(networkConfigurationsByChainId); -+ const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id; -+ if (isSelectedAccountIdChanged) { -+ __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f"); -+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, { -+ selectedAddress: selectedAccount.address, -+ chainIds, -+ }); -+ } -+ }); - }, _TokenDetectionController_stopPolling = function _TokenDetectionController_stopPolling() { - if (__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f")) { - clearInterval(__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f")); diff --git a/yarn.lock b/yarn.lock index 3825d237ad7..61868ccf2e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4377,18 +4377,27 @@ "@metamask/superstruct" "^3.1.0" "@metamask/utils" "^9.0.0" -"@metamask/accounts-controller@^20.0.1": - version "20.0.1" - resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-20.0.1.tgz#6f3dc905418ad75e44fce830b0363a585cce2ea8" - integrity sha512-zjAzkA3Okxy9aptBAHtFKCaTq5VNE8EHV48ETzxl8Jhdl8wpaTGWcacbqdkmOooC1lPLe2oXGglHSdBP73JDMQ== +"@metamask/abi-utils@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@metamask/abi-utils/-/abi-utils-3.0.0.tgz#2eab9cb895922b94305364d9111b6dde724f6f9b" + integrity sha512-a/l0DiSIr7+CBYVpHygUa3ztSlYLFCQMsklLna+t6qmNY9+eIO5TedNxhyIyvaJ+4cN7TLy0NQFbp9FV3X2ktg== + dependencies: + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.0.1" + +"@metamask/accounts-controller@^21.0.0": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@metamask/accounts-controller/-/accounts-controller-21.0.0.tgz#d4ef858cd9ec126423fe4a287edada1b9aa9d45a" + integrity sha512-Jt5knLn6n9DQ3IUsfjmtx6NjOTSZrUxHWdvU+SHtQxqkrtNlldqv1C+hQv4DxCTmk6MfSttuEdWObCaxpB2sMA== dependencies: "@ethereumjs/util" "^8.1.0" - "@metamask/base-controller" "^7.0.2" - "@metamask/eth-snap-keyring" "^5.0.1" - "@metamask/keyring-api" "^10.1.0" + "@metamask/base-controller" "^7.1.1" + "@metamask/eth-snap-keyring" "^8.0.0" + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-internal-api" "^2.0.0" "@metamask/snaps-sdk" "^6.7.0" "@metamask/snaps-utils" "^8.3.0" - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" deepmerge "^4.2.2" ethereum-cryptography "^2.1.2" immer "^9.0.6" @@ -4413,10 +4422,10 @@ "@metamask/utils" "^10.0.0" nanoid "^3.1.31" -"@metamask/assets-controllers@^45.1.1": - version "45.1.1" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-45.1.1.tgz#365be66cbd14a0fb5be57b3ec30d54f3314d70ec" - integrity sha512-zKMIKv9w4sZu0S/flbgYLAy3wgoL11GuL5RW8AF6n2/jKlMxEA0ImIK0EZGhmkJAB78ZrcVIdoLeEEvSsGuO/w== +"@metamask/assets-controllers@^46.0.0": + version "46.0.0" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-46.0.0.tgz#b3bb495eae490b24ea451d2e1b1a7fba7de9580e" + integrity sha512-iHaS+74ROQBynyLKUuzub5+kYVHuE29L8YJExpq4UJf4vnO7L5FTYEcNCplxBrCzS8qWinZ2RwS5OoUfvXYAKg== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/abi" "^5.7.0" @@ -4425,17 +4434,19 @@ "@ethersproject/contracts" "^5.7.0" "@ethersproject/providers" "^5.7.0" "@metamask/abi-utils" "^2.0.3" - "@metamask/base-controller" "^7.0.2" + "@metamask/base-controller" "^7.1.1" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.4.4" + "@metamask/controller-utils" "^11.4.5" "@metamask/eth-query" "^4.0.0" "@metamask/metamask-eth-abis" "^3.1.1" "@metamask/polling-controller" "^12.0.2" - "@metamask/rpc-errors" "^7.0.1" - "@metamask/utils" "^10.0.0" + "@metamask/rpc-errors" "^7.0.2" + "@metamask/snaps-utils" "^8.3.0" + "@metamask/utils" "^11.0.1" "@types/bn.js" "^5.1.5" "@types/uuid" "^8.3.0" async-mutex "^0.5.0" + bitcoin-address-validation "^2.2.3" bn.js "^5.2.1" cockatiel "^3.1.2" immer "^9.0.6" @@ -4444,12 +4455,12 @@ single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" -"@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-7.0.2.tgz#bf908858215cd4f7d072b3b0f7f0946cf886ee49" - integrity sha512-zeZ5QPKedGT/r2M1NsT4lE7z4u9ciSNcOXG2vUdmfA+QT9YLwIm5+t56UGku3ZTjKGxDn9Ukca3BEkRc57Gt0A== +"@metamask/base-controller@^7.0.1", "@metamask/base-controller@^7.0.2", "@metamask/base-controller@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-7.1.1.tgz#837216ee099563b2106202fa0ed376dc909dfbb9" + integrity sha512-4nbA6RL9y0SdHdn4MmMTREX6ISJL7OGHn0GXXszv0tp1fdjsn+SBs28uu1a9ceg1J7R/lO6JH7jAAz8zRtt8Nw== dependencies: - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" immer "^9.0.6" "@metamask/bitcoin-wallet-snap@^0.8.2": @@ -4491,15 +4502,15 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.5.0.tgz#33921fa9c15eb1863f55dcd5f75467ae15614ebb" integrity sha512-+j7jEcp0P1OUMEpa/OIwfJs/ahBC/akwgWxaRTSWX2SWABvlUKBVRMtslfL94Qj2wN2xw8xjaUy5nSHqrznqDA== -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.1", "@metamask/controller-utils@^11.4.4": - version "11.4.4" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.4.4.tgz#6e43e4cf53d34dad225bab8aaf4e7efcb1fe7623" - integrity sha512-0/gKC6jxlj8KRzi0RjGDQnml6l4b46Da/AIqnGJMOC59zl4qD5UN1GM+mq7L5duw/m8sSHa7VbL1hL0l7Cw1pg== +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.3.0", "@metamask/controller-utils@^11.4.1", "@metamask/controller-utils@^11.4.4", "@metamask/controller-utils@^11.4.5": + version "11.4.5" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.4.5.tgz#eb70dd403bc34e584a5b520956ef3bd71e7701e7" + integrity sha512-nSiZU0Yos6+cOtoBOzxMQKtdh3eRhUQJHVlH66fiyS/Eh3LARVMIcQ/BFvi3tcez35W21CnRzRbh2eBH9jeKoQ== dependencies: "@ethereumjs/util" "^8.1.0" "@metamask/eth-query" "^4.0.0" "@metamask/ethjs-unit" "^0.3.0" - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" "@spruceid/siwe-parser" "2.1.0" "@types/bn.js" "^5.1.5" bignumber.js "^9.1.2" @@ -4637,14 +4648,14 @@ ethereum-cryptography "^2.1.2" tweetnacl "^1.0.3" -"@metamask/eth-sig-util@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-8.0.0.tgz#6310d93cd1101cab3cc6bc2a1ff526290ed2695b" - integrity sha512-IwE6aoxUL39IhmsAgE4nk+OZbNo+ThFZRNsUjE1pjdEa4MFpWzm1Rue4zJ5DMy1oUyZBi/aiCLMhdMnjl2bh2Q== +"@metamask/eth-sig-util@^8.0.0", "@metamask/eth-sig-util@^8.1.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-8.1.2.tgz#8869bd9cdc989af7402812d5fa4d9a0f6cc30b98" + integrity sha512-+M7TKF8+RwqmfmDCfhgn7jDLtWfbpPCuBfkYPBpk9ptuqINu7+QzthNlU0Rn7jiJ//buyg2pModXVtpRBmgAeA== dependencies: "@ethereumjs/util" "^8.1.0" - "@metamask/abi-utils" "^2.0.4" - "@metamask/utils" "^9.0.0" + "@metamask/abi-utils" "^3.0.0" + "@metamask/utils" "^11.0.1" "@scure/base" "~1.1.3" ethereum-cryptography "^2.1.2" tweetnacl "^1.0.3" @@ -4660,20 +4671,45 @@ ethereum-cryptography "^2.1.2" randombytes "^2.1.0" -"@metamask/eth-snap-keyring@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@metamask/eth-snap-keyring/-/eth-snap-keyring-5.0.1.tgz#aa16a5d623a4c68b44afaa9cd47f45a409f922d1" - integrity sha512-XP/gmuLyI8jYItpFiqQUGB5/d+Qft6dCqs9vCcbMzTEvyatJtP0bkk2ZWmpfnrVGQIlepY/nhOD25NDE7mgwEg== +"@metamask/eth-snap-keyring@^7.0.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-snap-keyring/-/eth-snap-keyring-7.1.0.tgz#d472ff8c9abee1f438398d749408cd12ee44ada7" + integrity sha512-aOP8WkapqFmne7xt7Xo39YPxA3fvwSzKEO+Eo+o76r4rBAutH6QLNO9gmy6e4wm2TG9hHzsQjceZmLns75suvg== dependencies: "@ethereumjs/tx" "^4.2.0" - "@metamask/eth-sig-util" "^8.0.0" + "@metamask/eth-sig-util" "^8.1.2" + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-internal-api" "^1.1.0" + "@metamask/keyring-internal-snap-client" "^1.1.0" + "@metamask/keyring-utils" "^1.0.0" "@metamask/snaps-controllers" "^9.10.0" "@metamask/snaps-sdk" "^6.7.0" "@metamask/snaps-utils" "^8.3.0" "@metamask/superstruct" "^3.1.0" - "@metamask/utils" "^9.2.1" + "@metamask/utils" "^11.0.1" "@types/uuid" "^9.0.8" uuid "^9.0.1" + webextension-polyfill "^0.12.0" + +"@metamask/eth-snap-keyring@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eth-snap-keyring/-/eth-snap-keyring-8.0.0.tgz#60f6bdf3ed80b096172ebd983773fdc095a94c28" + integrity sha512-NLJmEcJYA+EAnX40N18aVlUZkXARHLDsJT7YoAtVBppRXZRNl9o5FGMe7xh5NrMdcy/Yss1TbQOnqyD0Ox2boA== + dependencies: + "@ethereumjs/tx" "^4.2.0" + "@metamask/eth-sig-util" "^8.1.2" + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-internal-api" "^2.0.0" + "@metamask/keyring-internal-snap-client" "^2.0.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/snaps-controllers" "^9.10.0" + "@metamask/snaps-sdk" "^6.7.0" + "@metamask/snaps-utils" "^8.3.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.0.1" + "@types/uuid" "^9.0.8" + uuid "^9.0.1" + webextension-polyfill "^0.12.0" "@metamask/etherscan-link@^2.0.0": version "2.1.0" @@ -4756,14 +4792,14 @@ bn.js "^5.2.1" uuid "^8.3.2" -"@metamask/json-rpc-engine@^10.0.0", "@metamask/json-rpc-engine@^10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-10.0.1.tgz#432e4b42770ecd4da8a89f94b52cdeac982bdca3" - integrity sha512-RmoKubUhK7BtZrllJjqMiSkW0p5QIKiO8ohJoa7/pewJIgPWzPFid/5EahQ4f/sPtTH9O9ypDQF9r7DFCPFSVQ== +"@metamask/json-rpc-engine@^10.0.0", "@metamask/json-rpc-engine@^10.0.1", "@metamask/json-rpc-engine@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-10.0.2.tgz#9173f90ebb16054fe20d5d73a910729a014750ce" + integrity sha512-UZKKvgEGVZyBOTKe0NrERv6J4QtR1X4a3Ppa10FZ2tY+nNvwQg3gFpWPRsYNQdPDFxtIsUdrMrqKvbkYSuHZkw== dependencies: - "@metamask/rpc-errors" "^7.0.1" + "@metamask/rpc-errors" "^7.0.2" "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" "@metamask/json-rpc-engine@^8.0.1": version "8.0.2" @@ -4784,14 +4820,14 @@ "@metamask/utils" "^8.3.0" readable-stream "^3.6.2" -"@metamask/json-rpc-middleware-stream@^8.0.2", "@metamask/json-rpc-middleware-stream@^8.0.5": - version "8.0.5" - resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-8.0.5.tgz#f91ba2ebc0285b2104f36e72359cc8ba33d08d75" - integrity sha512-g/1McYbBODSceBLA/rlSqzLyHcBCOsXok776Dh4PuCo5VjdLR11I24xPwR9VIdFVsrDd+MLH1q3xpS4loydLaw== +"@metamask/json-rpc-middleware-stream@^8.0.5", "@metamask/json-rpc-middleware-stream@^8.0.6": + version "8.0.6" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-8.0.6.tgz#3b8a6ccbdee2176285b05a3fe644d5f9a602669a" + integrity sha512-wE2CfuNZHnWbSjLEPCCb4MSyWgbQBUI5cslGZb+uRdXNzYOM/RDfq8FAdl6HhjmldHKdBFCW0L3kDr8frgahqA== dependencies: - "@metamask/json-rpc-engine" "^10.0.1" + "@metamask/json-rpc-engine" "^10.0.2" "@metamask/safe-event-emitter" "^3.0.0" - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" readable-stream "^3.6.2" "@metamask/key-tree@^10.0.0", "@metamask/key-tree@^10.0.1": @@ -4829,6 +4865,16 @@ uuid "^9.0.1" webextension-polyfill "^0.12.0" +"@metamask/keyring-api@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-api/-/keyring-api-13.0.0.tgz#593607911ffbab9df699bd3335603262f0ba12e5" + integrity sha512-8eemwtSzG3c4Q+zcWPKxRKBMuiApfvND7j0l4xt561wkksueaU2uF/SHUJ3MuPYtKh3Mg1gCcnj9sZ3rh1yRgA== + dependencies: + "@metamask/keyring-utils" "^1.0.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.0.1" + bech32 "^2.0.0" + "@metamask/keyring-controller@^19.0.1": version "19.0.1" resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-19.0.1.tgz#6fee40a46a780a720f4c864ea779673569be06a7" @@ -4848,6 +4894,84 @@ ethereumjs-wallet "^1.0.1" immer "^9.0.6" +"@metamask/keyring-internal-api@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-api/-/keyring-internal-api-1.1.0.tgz#3614c1a9d6f88e40421c2232789529cb395a2157" + integrity sha512-bKY7Iy0JfWyHK+E3HKrGgQrJM6TY2FjrBTaBiyc4Jrl1aOh55BIW57WygSkMvHT3rsBI/Vg3GWnq1io+7PG+Zw== + dependencies: + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.0.1" + +"@metamask/keyring-internal-api@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-api/-/keyring-internal-api-2.0.0.tgz#809b1acea178384bb704e8025d8557a30072f6f1" + integrity sha512-CG9MSt3CdcnIQpvgJ4StQqUkdVfv3YX0dXQuZG6czKtW+TNV/43xbgoaQuAk+XsisqfY5zMCmr+XTL3Wvwfc7Q== + dependencies: + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^11.0.1" + +"@metamask/keyring-internal-snap-client@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-snap-client/-/keyring-internal-snap-client-1.1.0.tgz#8e6bf842502f314fecb777c31a389f779c22bb62" + integrity sha512-5sl5c9QEZ7tCWLZgBXeDc0h/QquxYmnz5jetW5LEle1wa6WaUC/qryyt4FWe/Qy8mcMO05EIOMBDMKIQfea6ww== + dependencies: + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-snap-client" "^1.1.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/snaps-controllers" "^9.10.0" + "@metamask/snaps-sdk" "^6.7.0" + "@metamask/snaps-utils" "^8.3.0" + webextension-polyfill "^0.12.0" + +"@metamask/keyring-internal-snap-client@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-internal-snap-client/-/keyring-internal-snap-client-2.0.0.tgz#c44194af6d880c7b39fe583f4500e740a157fd8b" + integrity sha512-jfJkpsEgaUfbvT6gvqinZB72EnqZF1PkVByalAe2M9RGIvDkSkg6VNilgkEWXtzhz0xttaYD1wET6zJdmdaNFg== + dependencies: + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-snap-client" "^2.0.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/snaps-controllers" "^9.10.0" + "@metamask/snaps-sdk" "^6.7.0" + "@metamask/snaps-utils" "^8.3.0" + webextension-polyfill "^0.12.0" + +"@metamask/keyring-snap-client@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-snap-client/-/keyring-snap-client-1.1.0.tgz#0d215ed923d24bebb11721ffe093ea362176adce" + integrity sha512-Iv59YZlx/P67Jz9aq5XBE3AqS2TBXVcsGppw4busdhjgUG+vC9LXf7HeXwQmhnNh8IX8YAL03dX3cATg//d0KA== + dependencies: + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/superstruct" "^3.1.0" + "@types/uuid" "^9.0.8" + uuid "^9.0.1" + webextension-polyfill "^0.12.0" + +"@metamask/keyring-snap-client@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-snap-client/-/keyring-snap-client-2.0.0.tgz#395af45471ba8bf79e4778d5afd6dd56327b9e97" + integrity sha512-P6xR4sbYEp9vhg5yxTcPLDW1fFve1FHgYT72HS10KXZQvKlGgoOwZe8kcNpQGarqa/Cr4IwzpULJP8Xm/sAF+w== + dependencies: + "@metamask/keyring-api" "^13.0.0" + "@metamask/keyring-utils" "^1.0.0" + "@metamask/superstruct" "^3.1.0" + "@types/uuid" "^9.0.8" + uuid "^9.0.1" + webextension-polyfill "^0.12.0" + +"@metamask/keyring-utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@metamask/keyring-utils/-/keyring-utils-1.0.0.tgz#06a5df63c57304811ec56ac6e250c4628da435b8" + integrity sha512-adxVCKPHnai4w1+ZUNwL0T2DfxMpjcQucMKfa74oQuxoqjbTBDKeW6FzJwRzFspYEuRMhOLFOMUuZQQMgyF1OQ== + dependencies: + "@metamask/superstruct" "^3.1.0" + "@metamask/utils" "^9.3.0" + "@metamask/logging-controller@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@metamask/logging-controller/-/logging-controller-6.0.1.tgz#cfe858e91ba6fa490ebcf4e50bebd5f6dee0417e" @@ -5086,17 +5210,17 @@ readable-stream "^3.6.2" webextension-polyfill "^0.10.0" -"@metamask/providers@^18.1.0", "@metamask/providers@^18.1.1": - version "18.2.0" - resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-18.2.0.tgz#557ca488a58966e288e50d629f104b061f8a76d2" - integrity sha512-GYgweLy6N1/aNNAg4CLhtfu9XwNNG+MZNlPCAl6Ai92+GKCUFRp8RTTBN81EzTLJvJu7NKIrw99AApJACJ8KRA== +"@metamask/providers@^18.1.1", "@metamask/providers@^18.3.1": + version "18.3.1" + resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-18.3.1.tgz#b49351134cbe77034db67774cc4ff473e259158c" + integrity sha512-4wHCA24KDwq/eVnAu+/+N7BEuMNN63kdN295u8Wkdc76puyig3lJdcGGne+TEjiILG34twr9rjZPOWTCwOUcDg== dependencies: - "@metamask/json-rpc-engine" "^10.0.1" - "@metamask/json-rpc-middleware-stream" "^8.0.5" + "@metamask/json-rpc-engine" "^10.0.2" + "@metamask/json-rpc-middleware-stream" "^8.0.6" "@metamask/object-multiplex" "^2.0.0" - "@metamask/rpc-errors" "^7.0.1" + "@metamask/rpc-errors" "^7.0.2" "@metamask/safe-event-emitter" "^3.1.1" - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" detect-browser "^5.2.0" extension-port-stream "^4.1.0" fast-deep-equal "^3.1.3" @@ -5146,12 +5270,12 @@ "@metamask/utils" "^10.0.0" cockatiel "^3.1.2" -"@metamask/rpc-errors@7.0.1", "@metamask/rpc-errors@^6.2.1", "@metamask/rpc-errors@^7.0.0", "@metamask/rpc-errors@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-7.0.1.tgz#0eb2231a1d5e6bb102df5ac07f365c695bf70055" - integrity sha512-EeQGYioq845w2iBmiR9LHYqHhYIaeDTmxprHpPE3BTlkLB74P0xLv/TivOn4snNLowiC5ekOXfcUzCQszTDmSg== +"@metamask/rpc-errors@7.0.2", "@metamask/rpc-errors@^6.2.1", "@metamask/rpc-errors@^7.0.0", "@metamask/rpc-errors@^7.0.1", "@metamask/rpc-errors@^7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-7.0.2.tgz#d07b2ebfcf111556dfe93dc78699742ebe755359" + integrity sha512-YYYHsVYd46XwY2QZzpGeU4PSdRhHdxnzkB8piWGvJW2xbikZ3R+epAYEL4q/K8bh9JPTucsUdwRFnACor1aOYw== dependencies: - "@metamask/utils" "^10.0.0" + "@metamask/utils" "^11.0.1" fast-safe-stringify "^2.0.6" "@metamask/safe-event-emitter@^2.0.0": @@ -5415,10 +5539,10 @@ lodash "^4.17.21" uuid "^8.3.2" -"@metamask/utils@^10.0.0", "@metamask/utils@^10.0.1", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-10.0.1.tgz#a765f96c20e35fc51c068fb9f88a3332b40b215e" - integrity sha512-zHgAitJtRwviVVFnRUA2PLRMaAwatr3jiHgiH7mPicJaeSK4ma01aGR4fHy0iy5tlVo1ZiioTmJ1Hbp8FZ6pSg== +"@metamask/utils@^10.0.0", "@metamask/utils@^10.0.1", "@metamask/utils@^11.0.1", "@metamask/utils@^8.2.0", "@metamask/utils@^8.3.0", "@metamask/utils@^9.0.0", "@metamask/utils@^9.1.0", "@metamask/utils@^9.2.1", "@metamask/utils@^9.3.0": + version "11.0.1" + resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-11.0.1.tgz#16c4135489204fefe128b5e6c2b92c014453e1d5" + integrity sha512-tZlBvEJ6VhhfEiMV+Ad8rWRMjHKpbMogG01YU22JlsIeJptgIdZX1G8jJzhZH0Gxrixa2BeARh7m9lZWQo6rMg== dependencies: "@ethereumjs/tx" "^4.2.0" "@metamask/superstruct" "^3.1.0" @@ -12640,7 +12764,7 @@ bip66@^1.1.5: dependencies: safe-buffer "^5.0.1" -bitcoin-address-validation@2.2.3: +bitcoin-address-validation@2.2.3, bitcoin-address-validation@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz#ffae6d48facd5ce7ef60574891aab979d21f9828" integrity sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg==