diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 14e3bc0934d8..e6f4a0d4524e 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -302,7 +302,7 @@ async function getMetaMetricsEnabled() { function setSentryClient() { const clientOptions = getClientOptions(); - const { dsn, environment, release } = clientOptions; + const { dsn, environment, release, tracesSampleRate } = clientOptions; /** * Sentry throws on initialization as it wants to avoid polluting the global namespace and @@ -322,6 +322,7 @@ function setSentryClient() { environment, dsn, release, + tracesSampleRate, }); Sentry.registerSpanErrorInstrumentation(); diff --git a/package.json b/package.json index 2a90e76b51b3..0bc5cc2f07ac 100644 --- a/package.json +++ b/package.json @@ -367,9 +367,9 @@ "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "patch:@reduxjs/toolkit@npm%3A1.9.7#~/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch", "@segment/loosely-validate-event": "^2.0.0", - "@sentry/browser": "^8.19.0", - "@sentry/types": "^8.19.0", - "@sentry/utils": "^8.19.0", + "@sentry/browser": "^8.33.1", + "@sentry/types": "^8.33.1", + "@sentry/utils": "^8.33.1", "@swc/core": "1.4.11", "@trezor/connect-web": "patch:@trezor/connect-web@npm%3A9.3.0#~/.yarn/patches/@trezor-connect-web-npm-9.3.0-040ab10d9a.patch", "@zxing/browser": "^0.1.4", diff --git a/shared/lib/trace.test.ts b/shared/lib/trace.test.ts index 5154a930b7f9..7cd39eba03d1 100644 --- a/shared/lib/trace.test.ts +++ b/shared/lib/trace.test.ts @@ -1,4 +1,5 @@ import { + setMeasurement, Span, startSpan, startSpanManual, @@ -10,6 +11,7 @@ jest.mock('@sentry/browser', () => ({ withIsolationScope: jest.fn(), startSpan: jest.fn(), startSpanManual: jest.fn(), + setMeasurement: jest.fn(), })); const NAME_MOCK = TraceName.Transaction; @@ -32,7 +34,8 @@ describe('Trace', () => { const startSpanMock = jest.mocked(startSpan); const startSpanManualMock = jest.mocked(startSpanManual); const withIsolationScopeMock = jest.mocked(withIsolationScope); - const setTagsMock = jest.fn(); + const setMeasurementMock = jest.mocked(setMeasurement); + const setTagMock = jest.fn(); beforeEach(() => { jest.resetAllMocks(); @@ -41,13 +44,20 @@ describe('Trace', () => { startSpan: startSpanMock, startSpanManual: startSpanManualMock, withIsolationScope: withIsolationScopeMock, + setMeasurement: setMeasurementMock, }; startSpanMock.mockImplementation((_, fn) => fn({} as Span)); + startSpanManualMock.mockImplementation((_, fn) => + fn({} as Span, () => { + // Intentionally empty + }), + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any withIsolationScopeMock.mockImplementation((fn: any) => - fn({ setTags: setTagsMock }), + fn({ setTag: setTagMock }), ); }); @@ -91,8 +101,12 @@ describe('Trace', () => { expect.any(Function), ); - expect(setTagsMock).toHaveBeenCalledTimes(1); - expect(setTagsMock).toHaveBeenCalledWith(TAGS_MOCK); + expect(setTagMock).toHaveBeenCalledTimes(2); + expect(setTagMock).toHaveBeenCalledWith('tag1', 'value1'); + expect(setTagMock).toHaveBeenCalledWith('tag2', true); + + expect(setMeasurementMock).toHaveBeenCalledTimes(1); + expect(setMeasurementMock).toHaveBeenCalledWith('tag3', 123, 'none'); }); it('invokes Sentry if no callback provided', () => { @@ -117,8 +131,12 @@ describe('Trace', () => { expect.any(Function), ); - expect(setTagsMock).toHaveBeenCalledTimes(1); - expect(setTagsMock).toHaveBeenCalledWith(TAGS_MOCK); + expect(setTagMock).toHaveBeenCalledTimes(2); + expect(setTagMock).toHaveBeenCalledWith('tag1', 'value1'); + expect(setTagMock).toHaveBeenCalledWith('tag2', true); + + expect(setMeasurementMock).toHaveBeenCalledTimes(1); + expect(setMeasurementMock).toHaveBeenCalledWith('tag3', 123, 'none'); }); it('invokes Sentry if no callback provided with custom start time', () => { @@ -145,8 +163,12 @@ describe('Trace', () => { expect.any(Function), ); - expect(setTagsMock).toHaveBeenCalledTimes(1); - expect(setTagsMock).toHaveBeenCalledWith(TAGS_MOCK); + expect(setTagMock).toHaveBeenCalledTimes(2); + expect(setTagMock).toHaveBeenCalledWith('tag1', 'value1'); + expect(setTagMock).toHaveBeenCalledWith('tag2', true); + + expect(setMeasurementMock).toHaveBeenCalledTimes(1); + expect(setMeasurementMock).toHaveBeenCalledWith('tag3', 123, 'none'); }); }); diff --git a/shared/lib/trace.ts b/shared/lib/trace.ts index 0c667a346235..a067858a969c 100644 --- a/shared/lib/trace.ts +++ b/shared/lib/trace.ts @@ -1,10 +1,13 @@ import * as Sentry from '@sentry/browser'; -import { Primitive, StartSpanOptions } from '@sentry/types'; +import { MeasurementUnit, StartSpanOptions } from '@sentry/types'; import { createModuleLogger } from '@metamask/utils'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { log as sentryLogger } from '../../app/scripts/lib/setupSentry'; +/** + * The supported trace names. + */ export enum TraceName { BackgroundConnect = 'Background Connect', DeveloperTest = 'Developer Test', @@ -36,22 +39,71 @@ type PendingTrace = { startTime: number; }; +/** + * A context object to associate traces with each other and generate nested traces. + */ export type TraceContext = unknown; +/** + * A callback function that can be traced. + */ export type TraceCallback = (context?: TraceContext) => T; +/** + * A request to create a new trace. + */ export type TraceRequest = { + /** + * Custom data to associate with the trace. + */ data?: Record; + + /** + * A unique identifier when not tracing a callback. + * Defaults to 'default' if not provided. + */ id?: string; + + /** + * The name of the trace. + */ name: TraceName; + + /** + * The parent context of the trace. + * If provided, the trace will be nested under the parent trace. + */ parentContext?: TraceContext; + + /** + * Override the start time of the trace. + */ startTime?: number; + + /** + * Custom tags to associate with the trace. + */ tags?: Record; }; +/** + * A request to end a pending trace. + */ export type EndTraceRequest = { + /** + * The unique identifier of the trace. + * Defaults to 'default' if not provided. + */ id?: string; + + /** + * The name of the trace. + */ name: TraceName; + + /** + * Override the end time of the trace. + */ timestamp?: number; }; @@ -59,6 +111,16 @@ export function trace(request: TraceRequest, fn: TraceCallback): T; export function trace(request: TraceRequest): TraceContext; +/** + * Create a Sentry transaction to analyse the duration of a code flow. + * If a callback is provided, the transaction will be automatically ended when the callback completes. + * If the callback returns a promise, the transaction will be ended when the promise resolves or rejects. + * If no callback is provided, the transaction must be manually ended using `endTrace`. + * + * @param request - The data associated with the trace, such as the name and tags. + * @param fn - The optional callback to record the duration of. + * @returns The context of the trace, or the result of the callback if provided. + */ export function trace( request: TraceRequest, fn?: TraceCallback, @@ -70,6 +132,12 @@ export function trace( return traceCallback(request, fn); } +/** + * End a pending trace that was started without a callback. + * Does nothing if the pending trace cannot be found. + * + * @param request - The data necessary to identify and end the pending trace. + */ export function endTrace(request: EndTraceRequest) { const { name, timestamp } = request; const id = getTraceId(request); @@ -101,6 +169,10 @@ function traceCallback(request: TraceRequest, fn: TraceCallback): T { const start = Date.now(); let error: unknown; + if (span) { + initSpan(span, request); + } + return tryCatchMaybePromise( () => fn(span), (currentError) => { @@ -131,6 +203,10 @@ function startTrace(request: TraceRequest): TraceContext { span?.end(timestamp); }; + if (span) { + initSpan(span, request); + } + const pendingTrace = { end, request, startTime }; const key = getTraceKey(request); tracesByKey.set(key, pendingTrace); @@ -149,7 +225,7 @@ function startSpan( request: TraceRequest, callback: (spanOptions: StartSpanOptions) => T, ) { - const { data: attributes, name, parentContext, startTime, tags } = request; + const { data: attributes, name, parentContext, startTime } = request; const parentSpan = (parentContext ?? null) as Sentry.Span | null; const spanOptions: StartSpanOptions = { @@ -161,8 +237,7 @@ function startSpan( }; return sentryWithIsolationScope((scope: Sentry.Scope) => { - scope.setTags(tags as Record); - + initScope(scope, request); return callback(spanOptions); }); } @@ -182,6 +257,40 @@ function getPerformanceTimestamp(): number { return performance.timeOrigin + performance.now(); } +/** + * Initialise the isolated Sentry scope created for each trace. + * Includes setting all non-numeric tags. + * + * @param scope - The Sentry scope to initialise. + * @param request - The trace request. + */ +function initScope(scope: Sentry.Scope, request: TraceRequest) { + const tags = request.tags ?? {}; + + for (const [key, value] of Object.entries(tags)) { + if (typeof value !== 'number') { + scope.setTag(key, value); + } + } +} + +/** + * Initialise the Sentry span created for each trace. + * Includes setting all numeric tags as measurements so they can be queried numerically in Sentry. + * + * @param _span - The Sentry span to initialise. + * @param request - The trace request. + */ +function initSpan(_span: Sentry.Span, request: TraceRequest) { + const tags = request.tags ?? {}; + + for (const [key, value] of Object.entries(tags)) { + if (typeof value === 'number') { + sentrySetMeasurement(key, value, 'none'); + } + } +} + function tryCatchMaybePromise( tryFn: () => T, catchFn: (error: unknown) => void, @@ -251,3 +360,17 @@ function sentryWithIsolationScope(callback: (scope: Sentry.Scope) => T): T { return actual(callback); } + +function sentrySetMeasurement( + key: string, + value: number, + unit: MeasurementUnit, +) { + const actual = globalThis.sentry?.setMeasurement; + + if (!actual) { + return; + } + + actual(key, value, unit); +} diff --git a/ui/helpers/utils/tags.test.ts b/ui/helpers/utils/tags.test.ts new file mode 100644 index 000000000000..eae5e90f9ea1 --- /dev/null +++ b/ui/helpers/utils/tags.test.ts @@ -0,0 +1,206 @@ +// TODO: Remove restricted import +// eslint-disable-next-line import/no-restricted-paths +import { getEnvironmentType } from '../../../app/scripts/lib/util'; +import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../shared/constants/app'; +import { MetaMaskReduxState } from '../../store/store'; +import { getStartupTraceTags } from './tags'; + +jest.mock('../../../app/scripts/lib/util', () => ({ + ...jest.requireActual('../../../app/scripts/lib/util'), + getEnvironmentType: jest.fn(), +})); + +const STATE_EMPTY_MOCK = { + metamask: { + allTokens: {}, + internalAccounts: { + accounts: {}, + }, + metamaskNotificationsList: [], + }, +} as unknown as MetaMaskReduxState; + +function createMockState( + metamaskState: Partial, +): MetaMaskReduxState { + return { + ...STATE_EMPTY_MOCK, + metamask: { + ...STATE_EMPTY_MOCK.metamask, + ...metamaskState, + }, + }; +} + +describe('Tags Utils', () => { + const getEnvironmentTypeMock = jest.mocked(getEnvironmentType); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('getStartupTraceTags', () => { + it('includes UI type', () => { + getEnvironmentTypeMock.mockReturnValue(ENVIRONMENT_TYPE_FULLSCREEN); + + const tags = getStartupTraceTags(STATE_EMPTY_MOCK); + + expect(tags['wallet.ui_type']).toStrictEqual(ENVIRONMENT_TYPE_FULLSCREEN); + }); + + it('includes if unlocked', () => { + const state = createMockState({ isUnlocked: true }); + const tags = getStartupTraceTags(state); + + expect(tags['wallet.unlocked']).toStrictEqual(true); + }); + + it('includes if not unlocked', () => { + const state = createMockState({ isUnlocked: false }); + const tags = getStartupTraceTags(state); + + expect(tags['wallet.unlocked']).toStrictEqual(false); + }); + + it('includes pending approval type', () => { + const state = createMockState({ + pendingApprovals: { + 1: { + type: 'eth_sendTransaction', + }, + } as unknown as MetaMaskReduxState['metamask']['pendingApprovals'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.pending_approval']).toStrictEqual( + 'eth_sendTransaction', + ); + }); + + it('includes first pending approval type if multiple', () => { + const state = createMockState({ + pendingApprovals: { + 1: { + type: 'eth_sendTransaction', + }, + 2: { + type: 'personal_sign', + }, + } as unknown as MetaMaskReduxState['metamask']['pendingApprovals'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.pending_approval']).toStrictEqual( + 'eth_sendTransaction', + ); + }); + + it('includes account count', () => { + const state = createMockState({ + internalAccounts: { + accounts: { + '0x1234': {}, + '0x4321': {}, + }, + } as unknown as MetaMaskReduxState['metamask']['internalAccounts'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.account_count']).toStrictEqual(2); + }); + + it('includes nft count', () => { + const state = createMockState({ + allNfts: { + '0x1234': { + '0x1': [ + { + tokenId: '1', + }, + { + tokenId: '2', + }, + ], + '0x2': [ + { + tokenId: '3', + }, + { + tokenId: '4', + }, + ], + }, + '0x4321': { + '0x3': [ + { + tokenId: '5', + }, + ], + }, + } as unknown as MetaMaskReduxState['metamask']['allNfts'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.nft_count']).toStrictEqual(5); + }); + + it('includes notification count', () => { + const state = createMockState({ + metamaskNotificationsList: [ + {}, + {}, + {}, + ] as unknown as MetaMaskReduxState['metamask']['metamaskNotificationsList'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.notification_count']).toStrictEqual(3); + }); + + it('includes token count', () => { + const state = createMockState({ + allTokens: { + '0x1': { + '0x1234': [{}, {}], + '0x4321': [{}], + }, + '0x2': { + '0x5678': [{}], + }, + } as unknown as MetaMaskReduxState['metamask']['allTokens'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.token_count']).toStrictEqual(4); + }); + + it('includes transaction count', () => { + const state = createMockState({ + transactions: [ + { + id: 1, + chainId: '0x1', + }, + { + id: 2, + chainId: '0x1', + }, + { + id: 3, + chainId: '0x2', + }, + ] as unknown as MetaMaskReduxState['metamask']['transactions'], + }); + + const tags = getStartupTraceTags(state); + + expect(tags['wallet.transaction_count']).toStrictEqual(3); + }); + }); +}); diff --git a/ui/helpers/utils/tags.ts b/ui/helpers/utils/tags.ts new file mode 100644 index 000000000000..4a253e214d82 --- /dev/null +++ b/ui/helpers/utils/tags.ts @@ -0,0 +1,42 @@ +// TODO: Remove restricted import +// eslint-disable-next-line import/no-restricted-paths +import { getEnvironmentType } from '../../../app/scripts/lib/util'; +import { getIsUnlocked } from '../../ducks/metamask/metamask'; +import { + getInternalAccounts, + getPendingApprovals, + getTransactions, + selectAllTokensFlat, +} from '../../selectors'; +import { getMetamaskNotifications } from '../../selectors/metamask-notifications/metamask-notifications'; +import { selectAllNftsFlat } from '../../selectors/nft'; +import { MetaMaskReduxState } from '../../store/store'; + +/** + * Generate the required tags for the UI startup trace. + * + * @param state - The current flattened UI state. + * @returns The tags for the startup trace. + */ +export function getStartupTraceTags(state: MetaMaskReduxState) { + const uiType = getEnvironmentType(); + const unlocked = getIsUnlocked(state) as boolean; + const accountCount = getInternalAccounts(state).length; + const nftCount = selectAllNftsFlat(state).length; + const notificationCount = getMetamaskNotifications(state).length; + const tokenCount = selectAllTokensFlat(state).length as number; + const transactionCount = getTransactions(state).length; + const pendingApprovals = getPendingApprovals(state); + const firstApprovalType = pendingApprovals?.[0]?.type; + + return { + 'wallet.account_count': accountCount, + 'wallet.nft_count': nftCount, + 'wallet.notification_count': notificationCount, + 'wallet.pending_approval': firstApprovalType, + 'wallet.token_count': tokenCount, + 'wallet.transaction_count': transactionCount, + 'wallet.unlocked': unlocked, + 'wallet.ui_type': uiType, + }; +} diff --git a/ui/index.js b/ui/index.js index 5cb576e488d6..8cf2048cba41 100644 --- a/ui/index.js +++ b/ui/index.js @@ -39,6 +39,7 @@ import { import Root from './pages'; import txHelper from './helpers/utils/tx-helper'; import { setBackgroundConnection } from './store/background-connection'; +import { getStartupTraceTags } from './helpers/utils/tags'; log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn', false); @@ -182,8 +183,14 @@ export async function setupInitialStore( async function startApp(metamaskState, backgroundConnection, opts) { const { traceContext } = opts; + const tags = getStartupTraceTags({ metamask: metamaskState }); + const store = await trace( - { name: TraceName.SetupStore, parentContext: traceContext }, + { + name: TraceName.SetupStore, + parentContext: traceContext, + tags, + }, () => setupInitialStore(metamaskState, backgroundConnection, opts.activeTab), ); diff --git a/ui/selectors/nft.test.ts b/ui/selectors/nft.test.ts index 101eb4aae181..d6f4d956f020 100644 --- a/ui/selectors/nft.test.ts +++ b/ui/selectors/nft.test.ts @@ -38,6 +38,7 @@ describe('NFT Selectors', () => { [chainIdMock2]: [contractMock5], }, }, + allNfts: {}, }, }; @@ -80,6 +81,7 @@ describe('NFT Selectors', () => { [chainIdMock2]: [contractMock5], }, }, + allNfts: {}, }, }; diff --git a/ui/selectors/nft.ts b/ui/selectors/nft.ts index 8320c6258b1c..ab3836714923 100644 --- a/ui/selectors/nft.ts +++ b/ui/selectors/nft.ts @@ -1,14 +1,19 @@ -import { NftContract } from '@metamask/assets-controllers'; +import { Nft, NftContract } from '@metamask/assets-controllers'; import { createSelector } from 'reselect'; import { getMemoizedCurrentChainId } from './selectors'; -type NftState = { +export type NftState = { metamask: { allNftContracts: { [account: string]: { [chainId: string]: NftContract[]; }; }; + allNfts: { + [account: string]: { + [chainId: string]: Nft[]; + }; + }; }; }; @@ -16,6 +21,16 @@ function getNftContractsByChainByAccount(state: NftState) { return state.metamask.allNftContracts ?? {}; } +/** + * Get all NFTs owned by the user. + * + * @param state - Metamask state. + * @returns All NFTs owned by the user, keyed by chain ID then account address. + */ +function getNftsByChainByAccount(state: NftState) { + return state.metamask.allNfts ?? {}; +} + export const getNftContractsByAddressByChain = createSelector( getNftContractsByChainByAccount, (nftContractsByChainByAccount) => { @@ -53,3 +68,21 @@ export const getNftContractsByAddressOnCurrentChain = createSelector( return nftContractsByAddressByChain[currentChainId] ?? {}; }, ); + +/** + * Get a flattened list of all NFTs owned by the user. + * Includes all NFTs from all chains and accounts. + * + * @param state - Metamask state. + * @returns All NFTs owned by the user in a single array. + */ +export const selectAllNftsFlat = createSelector( + getNftsByChainByAccount, + (nftsByChainByAccount) => { + const nftsByChainArray = Object.values(nftsByChainByAccount); + return nftsByChainArray.reduce((acc, nftsByChain) => { + const nftsArrays = Object.values(nftsByChain); + return acc.concat(...nftsArrays); + }, []); + }, +); diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 644924a41e3e..17e6ffc4500a 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -490,6 +490,24 @@ export function getAllTokens(state) { return state.metamask.allTokens; } +/** + * Get a flattened list of all ERC-20 tokens owned by the user. + * Includes all tokens from all chains and accounts. + * + * @returns {object[]} All ERC-20 tokens owned by the user in a flat array. + */ +export const selectAllTokensFlat = createSelector( + getAllTokens, + (tokensByAccountByChain) => { + const tokensByAccountArray = Object.values(tokensByAccountByChain); + + return tokensByAccountArray.reduce((acc, tokensByAccount) => { + const tokensArray = Object.values(tokensByAccount); + return acc.concat(...tokensArray); + }, []); + }, +); + /** * Selector to return an origin to network ID map * diff --git a/ui/store/store.ts b/ui/store/store.ts index 6e580c137bdc..8433511380e7 100644 --- a/ui/store/store.ts +++ b/ui/store/store.ts @@ -5,6 +5,11 @@ import { ApprovalControllerState } from '@metamask/approval-controller'; import { GasEstimateType, GasFeeEstimates } from '@metamask/gas-fee-controller'; import { TransactionMeta } from '@metamask/transaction-controller'; import { InternalAccount } from '@metamask/keyring-api'; +import { + NftControllerState, + TokensControllerState, +} from '@metamask/assets-controllers'; +import { NotificationServicesControllerState } from '@metamask/notification-services-controller/notification-services'; import rootReducer from '../ducks'; import { LedgerTransportTypes } from '../../shared/constants/hardware-wallets'; import type { NetworkStatus } from '../../shared/constants/network'; @@ -45,48 +50,50 @@ export type MessagesIndexedById = { * state received from the background takes precedence over anything in the * metamask reducer. */ -type TemporaryBackgroundState = { - addressBook: { - [chainId: string]: { - name: string; - }[]; - }; - // todo: can this be deleted post network controller v20 - providerConfig: { - chainId: string; - }; - transactions: TransactionMeta[]; - ledgerTransportType: LedgerTransportTypes; - unapprovedDecryptMsgs: MessagesIndexedById; - unapprovedPersonalMsgs: MessagesIndexedById; - unapprovedTypedMessages: MessagesIndexedById; - networksMetadata: { - [NetworkClientId: string]: { - EIPS: { [eip: string]: boolean }; - status: NetworkStatus; +type TemporaryBackgroundState = NftControllerState & + NotificationServicesControllerState & + TokensControllerState & { + addressBook: { + [chainId: string]: { + name: string; + }[]; }; - }; - selectedNetworkClientId: string; - pendingApprovals: ApprovalControllerState['pendingApprovals']; - approvalFlows: ApprovalControllerState['approvalFlows']; - knownMethodData?: { - [fourBytePrefix: string]: Record; - }; - gasFeeEstimates: GasFeeEstimates; - gasEstimateType: GasEstimateType; - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - custodyAccountDetails?: { [key: string]: any }; - ///: END:ONLY_INCLUDE_IF - internalAccounts: { - accounts: { - [key: string]: InternalAccount; + // todo: can this be deleted post network controller v20 + providerConfig: { + chainId: string; + }; + transactions: TransactionMeta[]; + ledgerTransportType: LedgerTransportTypes; + unapprovedDecryptMsgs: MessagesIndexedById; + unapprovedPersonalMsgs: MessagesIndexedById; + unapprovedTypedMessages: MessagesIndexedById; + networksMetadata: { + [NetworkClientId: string]: { + EIPS: { [eip: string]: boolean }; + status: NetworkStatus; + }; + }; + selectedNetworkClientId: string; + pendingApprovals: ApprovalControllerState['pendingApprovals']; + approvalFlows: ApprovalControllerState['approvalFlows']; + knownMethodData?: { + [fourBytePrefix: string]: Record; }; - selectedAccount: string; + gasFeeEstimates: GasFeeEstimates; + gasEstimateType: GasEstimateType; + ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) + // TODO: Replace `any` with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + custodyAccountDetails?: { [key: string]: any }; + ///: END:ONLY_INCLUDE_IF + internalAccounts: { + accounts: { + [key: string]: InternalAccount; + }; + selectedAccount: string; + }; + keyrings: { type: string; accounts: string[] }[]; }; - keyrings: { type: string; accounts: string[] }[]; -}; type RootReducerReturnType = ReturnType; diff --git a/yarn.lock b/yarn.lock index 90fe3bc46886..e8cade3e2727 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7897,7 +7897,7 @@ __metadata: languageName: node linkType: hard -"@sentry/browser@npm:^8.19.0": +"@sentry/browser@npm:^8.33.1": version: 8.33.1 resolution: "@sentry/browser@npm:8.33.1" dependencies: @@ -7937,14 +7937,14 @@ __metadata: languageName: node linkType: hard -"@sentry/types@npm:8.33.1, @sentry/types@npm:^8.19.0": +"@sentry/types@npm:8.33.1, @sentry/types@npm:^8.33.1": version: 8.33.1 resolution: "@sentry/types@npm:8.33.1" checksum: 10/bcd7f80e84a23cb810fa5819dc85f45bd62d52b01b1f64a1b31297df21e9d1f4de8f7ea91835c5d6a7010d7dbfc8b09cd708d057d345a6ff685b7f12db41ae57 languageName: node linkType: hard -"@sentry/utils@npm:8.33.1, @sentry/utils@npm:^8.19.0": +"@sentry/utils@npm:8.33.1, @sentry/utils@npm:^8.33.1": version: 8.33.1 resolution: "@sentry/utils@npm:8.33.1" dependencies: @@ -26143,10 +26143,10 @@ __metadata: "@popperjs/core": "npm:^2.4.0" "@reduxjs/toolkit": "patch:@reduxjs/toolkit@npm%3A1.9.7#~/.yarn/patches/@reduxjs-toolkit-npm-1.9.7-b14925495c.patch" "@segment/loosely-validate-event": "npm:^2.0.0" - "@sentry/browser": "npm:^8.19.0" + "@sentry/browser": "npm:^8.33.1" "@sentry/cli": "npm:^2.19.4" - "@sentry/types": "npm:^8.19.0" - "@sentry/utils": "npm:^8.19.0" + "@sentry/types": "npm:^8.33.1" + "@sentry/utils": "npm:^8.33.1" "@storybook/addon-a11y": "npm:^7.6.20" "@storybook/addon-actions": "npm:^7.6.20" "@storybook/addon-designs": "npm:^7.0.9"