From 2a7fab70bc88b451bddf221534145640abb3c41c Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 4 Sep 2025 17:55:28 -0230 Subject: [PATCH 1/3] feat: Add new metadata to confirmation controllers The new metadata properties `includeInStateLogs` and `usedInUi` have been added to all controllers maintained by the confirmations team. Relates to #6443 --- .../src/AddressBookController.test.ts | 64 +++++- .../src/AddressBookController.ts | 7 +- .../src/ApprovalController.test.ts | 60 ++++- .../src/ApprovalController.ts | 21 +- .../ens-controller/src/EnsController.test.ts | 212 +++++++++++++++++- packages/ens-controller/src/EnsController.ts | 14 +- .../src/GasFeeController.test.ts | 71 +++++- .../src/GasFeeController.ts | 30 ++- .../src/LoggingController.test.ts | 76 ++++++- .../src/LoggingController.ts | 7 +- .../src/AbstractMessageManager.test.ts | 65 +++++- .../src/AbstractMessageManager.ts | 14 +- .../src/NameController.test.ts | 85 +++++++ .../name-controller/src/NameController.ts | 14 +- .../src/SignatureController.test.ts | 67 ++++++ .../src/SignatureController.ts | 35 ++- .../src/TransactionController.test.ts | 74 +++++- .../src/TransactionController.ts | 10 + .../src/UserOperationController.test.ts | 63 ++++++ .../src/UserOperationController.ts | 7 +- 20 files changed, 968 insertions(+), 28 deletions(-) diff --git a/packages/address-book-controller/src/AddressBookController.test.ts b/packages/address-book-controller/src/AddressBookController.test.ts index 060948a59cb..0a918ce8716 100644 --- a/packages/address-book-controller/src/AddressBookController.test.ts +++ b/packages/address-book-controller/src/AddressBookController.test.ts @@ -1,4 +1,4 @@ -import { Messenger } from '@metamask/base-controller'; +import { Messenger, deriveStateFromMetadata } from '@metamask/base-controller'; import { toHex } from '@metamask/controller-utils'; import type { Hex } from '@metamask/utils'; @@ -618,4 +618,66 @@ describe('AddressBookController', () => { expect(chain2Contacts).toHaveLength(2); expect(chain137Contacts).toHaveLength(1); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const { controller } = arrangeMocks(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const { controller } = arrangeMocks(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "addressBook": Object {}, + } + `); + }); + + it('persists expected state', () => { + const { controller } = arrangeMocks(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "addressBook": Object {}, + } + `); + }); + + it('exposes expected state to UI', () => { + const { controller } = arrangeMocks(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "addressBook": Object {}, + } + `); + }); + }); }); diff --git a/packages/address-book-controller/src/AddressBookController.ts b/packages/address-book-controller/src/AddressBookController.ts index b7637b22049..e02200b0710 100644 --- a/packages/address-book-controller/src/AddressBookController.ts +++ b/packages/address-book-controller/src/AddressBookController.ts @@ -147,7 +147,12 @@ export type AddressBookControllerEvents = | AddressBookControllerContactDeletedEvent; const addressBookControllerMetadata = { - addressBook: { persist: true, anonymous: false }, + addressBook: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, }; /** diff --git a/packages/approval-controller/src/ApprovalController.test.ts b/packages/approval-controller/src/ApprovalController.test.ts index 18cc824b451..e817a38ed56 100644 --- a/packages/approval-controller/src/ApprovalController.test.ts +++ b/packages/approval-controller/src/ApprovalController.test.ts @@ -1,6 +1,6 @@ /* eslint-disable jest/expect-expect */ -import { Messenger } from '@metamask/base-controller'; +import { deriveStateFromMetadata, Messenger } from '@metamask/base-controller'; import { errorCodes, JsonRpcError } from '@metamask/rpc-errors'; import { nanoid } from 'nanoid'; @@ -1712,4 +1712,62 @@ describe('approval controller', () => { }); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + expect( + deriveStateFromMetadata( + approvalController.state, + approvalController.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(` + Object { + "pendingApprovals": Object {}, + } + `); + }); + + it('includes expected state in state logs', () => { + expect( + deriveStateFromMetadata( + approvalController.state, + approvalController.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "approvalFlows": Array [], + "pendingApprovalCount": 0, + "pendingApprovals": Object {}, + } + `); + }); + + it('persists expected state', () => { + expect( + deriveStateFromMetadata( + approvalController.state, + approvalController.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('exposes expected state to UI', () => { + expect( + deriveStateFromMetadata( + approvalController.state, + approvalController.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "approvalFlows": Array [], + "pendingApprovalCount": 0, + "pendingApprovals": Object {}, + } + `); + }); + }); }); diff --git a/packages/approval-controller/src/ApprovalController.ts b/packages/approval-controller/src/ApprovalController.ts index 5b7398a83f8..9f577677cf5 100644 --- a/packages/approval-controller/src/ApprovalController.ts +++ b/packages/approval-controller/src/ApprovalController.ts @@ -27,9 +27,24 @@ export const APPROVAL_TYPE_RESULT_SUCCESS = 'result_success'; const controllerName = 'ApprovalController'; const stateMetadata = { - pendingApprovals: { persist: false, anonymous: true }, - pendingApprovalCount: { persist: false, anonymous: false }, - approvalFlows: { persist: false, anonymous: false }, + pendingApprovals: { + includeInStateLogs: true, + persist: false, + anonymous: true, + usedInUi: true, + }, + pendingApprovalCount: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, + approvalFlows: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, }; const getAlreadyPendingMessage = (origin: string, type: string) => diff --git a/packages/ens-controller/src/EnsController.test.ts b/packages/ens-controller/src/EnsController.test.ts index 9643074a27d..546fbbc0195 100644 --- a/packages/ens-controller/src/EnsController.test.ts +++ b/packages/ens-controller/src/EnsController.test.ts @@ -1,5 +1,5 @@ import * as providersModule from '@ethersproject/providers'; -import { Messenger } from '@metamask/base-controller'; +import { Messenger, deriveStateFromMetadata } from '@metamask/base-controller'; import { toChecksumHexAddress, toHex, @@ -704,4 +704,214 @@ describe('EnsController', () => { expect(await ens.reverseResolveAddress(address1)).toBeUndefined(); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const rootMessenger = getRootMessenger(); + const ensControllerMessenger = getRestrictedMessenger(rootMessenger); + const controller = new EnsController({ + messenger: ensControllerMessenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const rootMessenger = getRootMessenger(); + const ensControllerMessenger = getRestrictedMessenger(rootMessenger); + const controller = new EnsController({ + messenger: ensControllerMessenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "ensEntries": Object { + "0x1": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x1", + "ensName": ".", + }, + }, + "0x3": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x3", + "ensName": ".", + }, + }, + "0x4": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x4", + "ensName": ".", + }, + }, + "0x4268": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x4268", + "ensName": ".", + }, + }, + "0x5": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x5", + "ensName": ".", + }, + }, + "0xaa36a7": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0xaa36a7", + "ensName": ".", + }, + }, + }, + "ensResolutionsByAddress": Object {}, + } + `); + }); + + it('persists expected state', () => { + const rootMessenger = getRootMessenger(); + const ensControllerMessenger = getRestrictedMessenger(rootMessenger); + const controller = new EnsController({ + messenger: ensControllerMessenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "ensEntries": Object { + "0x1": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x1", + "ensName": ".", + }, + }, + "0x3": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x3", + "ensName": ".", + }, + }, + "0x4": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x4", + "ensName": ".", + }, + }, + "0x4268": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x4268", + "ensName": ".", + }, + }, + "0x5": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x5", + "ensName": ".", + }, + }, + "0xaa36a7": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0xaa36a7", + "ensName": ".", + }, + }, + }, + "ensResolutionsByAddress": Object {}, + } + `); + }); + + it('exposes expected state to UI', () => { + const rootMessenger = getRootMessenger(); + const ensControllerMessenger = getRestrictedMessenger(rootMessenger); + const controller = new EnsController({ + messenger: ensControllerMessenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "ensEntries": Object { + "0x1": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x1", + "ensName": ".", + }, + }, + "0x3": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x3", + "ensName": ".", + }, + }, + "0x4": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x4", + "ensName": ".", + }, + }, + "0x4268": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x4268", + "ensName": ".", + }, + }, + "0x5": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0x5", + "ensName": ".", + }, + }, + "0xaa36a7": Object { + ".": Object { + "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "chainId": "0xaa36a7", + "ensName": ".", + }, + }, + }, + "ensResolutionsByAddress": Object {}, + } + `); + }); + }); }); diff --git a/packages/ens-controller/src/EnsController.ts b/packages/ens-controller/src/EnsController.ts index 1dba71cb6ae..14023726482 100644 --- a/packages/ens-controller/src/EnsController.ts +++ b/packages/ens-controller/src/EnsController.ts @@ -82,8 +82,18 @@ export type EnsControllerMessenger = RestrictedMessenger< >; const metadata = { - ensEntries: { persist: true, anonymous: false }, - ensResolutionsByAddress: { persist: true, anonymous: false }, + ensEntries: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, + ensResolutionsByAddress: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, }; const defaultState = { diff --git a/packages/gas-fee-controller/src/GasFeeController.test.ts b/packages/gas-fee-controller/src/GasFeeController.test.ts index 53b1f821e82..4153f3829b2 100644 --- a/packages/gas-fee-controller/src/GasFeeController.test.ts +++ b/packages/gas-fee-controller/src/GasFeeController.test.ts @@ -1,4 +1,4 @@ -import { Messenger } from '@metamask/base-controller'; +import { deriveStateFromMetadata, Messenger } from '@metamask/base-controller'; import { ChainId, convertHexToDecimal, @@ -1266,4 +1266,73 @@ describe('GasFeeController', () => { ); }); }); + + describe('metadata', () => { + beforeEach(async () => { + await setupGasFeeController(); + }); + + it('includes expected state in debug snapshots', () => { + expect( + deriveStateFromMetadata( + gasFeeController.state, + gasFeeController.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + expect( + deriveStateFromMetadata( + gasFeeController.state, + gasFeeController.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "estimatedGasFeeTimeBounds": Object {}, + "gasEstimateType": "none", + "gasFeeEstimates": Object {}, + "gasFeeEstimatesByChainId": Object {}, + "nonRPCGasFeeApisDisabled": false, + } + `); + }); + + it('persists expected state', () => { + expect( + deriveStateFromMetadata( + gasFeeController.state, + gasFeeController.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "estimatedGasFeeTimeBounds": Object {}, + "gasEstimateType": "none", + "gasFeeEstimates": Object {}, + "gasFeeEstimatesByChainId": Object {}, + "nonRPCGasFeeApisDisabled": false, + } + `); + }); + + it('exposes expected state to UI', () => { + expect( + deriveStateFromMetadata( + gasFeeController.state, + gasFeeController.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "estimatedGasFeeTimeBounds": Object {}, + "gasEstimateType": "none", + "gasFeeEstimates": Object {}, + "gasFeeEstimatesByChainId": Object {}, + } + `); + }); + }); }); diff --git a/packages/gas-fee-controller/src/GasFeeController.ts b/packages/gas-fee-controller/src/GasFeeController.ts index c26a08ee28b..6a50f0cfbdf 100644 --- a/packages/gas-fee-controller/src/GasFeeController.ts +++ b/packages/gas-fee-controller/src/GasFeeController.ts @@ -162,13 +162,35 @@ type FallbackGasFeeEstimates = { const metadata = { gasFeeEstimatesByChainId: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: true, + }, + gasFeeEstimates: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, + estimatedGasFeeTimeBounds: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, + gasEstimateType: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, + nonRPCGasFeeApisDisabled: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: false, }, - gasFeeEstimates: { persist: true, anonymous: false }, - estimatedGasFeeTimeBounds: { persist: true, anonymous: false }, - gasEstimateType: { persist: true, anonymous: false }, - nonRPCGasFeeApisDisabled: { persist: true, anonymous: false }, }; export type GasFeeStateEthGasPrice = { diff --git a/packages/logging-controller/src/LoggingController.test.ts b/packages/logging-controller/src/LoggingController.test.ts index 929cbb42f98..7c7d89023ab 100644 --- a/packages/logging-controller/src/LoggingController.test.ts +++ b/packages/logging-controller/src/LoggingController.test.ts @@ -1,4 +1,4 @@ -import { Messenger } from '@metamask/base-controller'; +import { Messenger, deriveStateFromMetadata } from '@metamask/base-controller'; import * as uuid from 'uuid'; import type { LoggingControllerActions } from './LoggingController'; @@ -183,4 +183,78 @@ describe('LoggingController', () => { const logs = Object.values(controller.state.logs); expect(logs).toHaveLength(0); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const controller = new LoggingController({ + messenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const controller = new LoggingController({ + messenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "logs": Object {}, + } + `); + }); + + it('persists expected state', () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const controller = new LoggingController({ + messenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "logs": Object {}, + } + `); + }); + + it('exposes expected state to UI', () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const controller = new LoggingController({ + messenger, + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + }); }); diff --git a/packages/logging-controller/src/LoggingController.ts b/packages/logging-controller/src/LoggingController.ts index 384108d78fa..538b201a74b 100644 --- a/packages/logging-controller/src/LoggingController.ts +++ b/packages/logging-controller/src/LoggingController.ts @@ -63,7 +63,12 @@ export type LoggingControllerMessenger = RestrictedMessenger< >; const metadata = { - logs: { persist: true, anonymous: false }, + logs: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: false, + }, }; const defaultState = { diff --git a/packages/message-manager/src/AbstractMessageManager.test.ts b/packages/message-manager/src/AbstractMessageManager.test.ts index 6914beccdd1..196f42a1df1 100644 --- a/packages/message-manager/src/AbstractMessageManager.test.ts +++ b/packages/message-manager/src/AbstractMessageManager.test.ts @@ -1,4 +1,7 @@ -import type { RestrictedMessenger } from '@metamask/base-controller'; +import { + deriveStateFromMetadata, + type RestrictedMessenger, +} from '@metamask/base-controller'; import { ApprovalType } from '@metamask/controller-utils'; import type { @@ -567,4 +570,64 @@ describe('AbstractTestManager', () => { expect(controller.getUnapprovedMessagesCount()).toBe(0); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const controller = new AbstractTestManager(MOCK_INITIAL_OPTIONS); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const controller = new AbstractTestManager(MOCK_INITIAL_OPTIONS); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "unapprovedMessages": Object {}, + "unapprovedMessagesCount": 0, + } + `); + }); + + it('persists expected state', () => { + const controller = new AbstractTestManager(MOCK_INITIAL_OPTIONS); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('exposes expected state to UI', () => { + const controller = new AbstractTestManager(MOCK_INITIAL_OPTIONS); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "unapprovedMessages": Object {}, + "unapprovedMessagesCount": 0, + } + `); + }); + }); }); diff --git a/packages/message-manager/src/AbstractMessageManager.ts b/packages/message-manager/src/AbstractMessageManager.ts index 172a1230766..14c20d85edd 100644 --- a/packages/message-manager/src/AbstractMessageManager.ts +++ b/packages/message-manager/src/AbstractMessageManager.ts @@ -13,8 +13,18 @@ import type { Draft } from 'immer'; import { v1 as random } from 'uuid'; const stateMetadata = { - unapprovedMessages: { persist: false, anonymous: false }, - unapprovedMessagesCount: { persist: false, anonymous: false }, + unapprovedMessages: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, + unapprovedMessagesCount: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, }; const getDefaultState = () => ({ diff --git a/packages/name-controller/src/NameController.test.ts b/packages/name-controller/src/NameController.test.ts index 6d9eaf1b618..83b197261fd 100644 --- a/packages/name-controller/src/NameController.test.ts +++ b/packages/name-controller/src/NameController.test.ts @@ -1,3 +1,5 @@ +import { deriveStateFromMetadata } from '@metamask/base-controller'; + import type { SetNameRequest, UpdateProposedNamesRequest, @@ -2751,4 +2753,87 @@ describe('NameController', () => { }); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [createMockProvider(1)], + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [createMockProvider(1)], + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "nameSources": Object {}, + "names": Object { + "ethereumAddress": Object {}, + }, + } + `); + }); + + it('persists expected state', () => { + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [createMockProvider(1)], + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "nameSources": Object {}, + "names": Object { + "ethereumAddress": Object {}, + }, + } + `); + }); + + it('exposes expected state to UI', () => { + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [createMockProvider(1)], + }); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "nameSources": Object {}, + "names": Object { + "ethereumAddress": Object {}, + }, + } + `); + }); + }); }); diff --git a/packages/name-controller/src/NameController.ts b/packages/name-controller/src/NameController.ts index cf44af82f51..b37327916e6 100644 --- a/packages/name-controller/src/NameController.ts +++ b/packages/name-controller/src/NameController.ts @@ -41,8 +41,18 @@ const DEFAULT_VARIATION = ''; const controllerName = 'NameController'; const stateMetadata = { - names: { persist: true, anonymous: false }, - nameSources: { persist: true, anonymous: false }, + names: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, + nameSources: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, }; const getDefaultState = () => ({ diff --git a/packages/signature-controller/src/SignatureController.test.ts b/packages/signature-controller/src/SignatureController.test.ts index 8a5c53b3982..ef759e17b52 100644 --- a/packages/signature-controller/src/SignatureController.test.ts +++ b/packages/signature-controller/src/SignatureController.test.ts @@ -1,3 +1,4 @@ +import { deriveStateFromMetadata } from '@metamask/base-controller'; import type { SIWEMessage } from '@metamask/controller-utils'; import { detectSIWE, ORIGIN_METAMASK } from '@metamask/controller-utils'; import { SignTypedDataVersion } from '@metamask/keyring-controller'; @@ -1300,4 +1301,70 @@ describe('SignatureController', () => { ); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const { controller } = createController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const { controller } = createController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "signatureRequests": Object {}, + "unapprovedPersonalMsgCount": 0, + "unapprovedPersonalMsgs": Object {}, + "unapprovedTypedMessages": Object {}, + "unapprovedTypedMessagesCount": 0, + } + `); + }); + + it('persists expected state', () => { + const { controller } = createController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('exposes expected state to UI', () => { + const { controller } = createController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "signatureRequests": Object {}, + "unapprovedPersonalMsgCount": 0, + "unapprovedPersonalMsgs": Object {}, + "unapprovedTypedMessages": Object {}, + "unapprovedTypedMessagesCount": 0, + } + `); + }); + }); }); diff --git a/packages/signature-controller/src/SignatureController.ts b/packages/signature-controller/src/SignatureController.ts index 26b80542cef..aa2f195b217 100644 --- a/packages/signature-controller/src/SignatureController.ts +++ b/packages/signature-controller/src/SignatureController.ts @@ -60,11 +60,36 @@ import { const controllerName = 'SignatureController'; const stateMetadata = { - signatureRequests: { persist: false, anonymous: false }, - unapprovedPersonalMsgs: { persist: false, anonymous: false }, - unapprovedTypedMessages: { persist: false, anonymous: false }, - unapprovedPersonalMsgCount: { persist: false, anonymous: false }, - unapprovedTypedMessagesCount: { persist: false, anonymous: false }, + signatureRequests: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, + unapprovedPersonalMsgs: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, + unapprovedTypedMessages: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, + unapprovedPersonalMsgCount: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, + unapprovedTypedMessagesCount: { + includeInStateLogs: true, + persist: false, + anonymous: false, + usedInUi: true, + }, }; const getDefaultState = () => ({ diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index c789b3ea585..80405a42225 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -4,7 +4,7 @@ import type { AddApprovalRequest, AddResult, } from '@metamask/approval-controller'; -import { Messenger } from '@metamask/base-controller'; +import { Messenger, deriveStateFromMetadata } from '@metamask/base-controller'; import { ChainId, NetworkType, @@ -7972,4 +7972,76 @@ describe('TransactionController', () => { ).toThrow('No matching gas fee token found'); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const { controller } = setupController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const { controller } = setupController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "lastFetchedBlockNumbers": Object {}, + "methodData": Object {}, + "submitHistory": Array [], + "transactionBatches": Array [], + "transactions": Array [], + } + `); + }); + + it('persists expected state', () => { + const { controller } = setupController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "lastFetchedBlockNumbers": Object {}, + "methodData": Object {}, + "submitHistory": Array [], + "transactionBatches": Array [], + "transactions": Array [], + } + `); + }); + + it('exposes expected state to UI', () => { + const { controller } = setupController(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "methodData": Object {}, + "transactionBatches": Array [], + "transactions": Array [], + } + `); + }); + }); }); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index 3daa5e24bbf..07d928c4385 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -184,24 +184,34 @@ import { */ const metadata = { transactions: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: true, }, transactionBatches: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: true, }, methodData: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: true, }, lastFetchedBlockNumbers: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: false, }, submitHistory: { + includeInStateLogs: true, persist: true, anonymous: false, + usedInUi: false, }, }; diff --git a/packages/user-operation-controller/src/UserOperationController.test.ts b/packages/user-operation-controller/src/UserOperationController.test.ts index 027fbb1fcd2..9868e09cd56 100644 --- a/packages/user-operation-controller/src/UserOperationController.test.ts +++ b/packages/user-operation-controller/src/UserOperationController.test.ts @@ -34,6 +34,7 @@ import { validateSignUserOperationResponse, validateUpdateUserOperationResponse, } from './utils/validation'; +import { deriveStateFromMetadata } from '@metamask/base-controller'; jest.mock('@metamask/transaction-controller'); jest.mock('./utils/gas'); @@ -1443,4 +1444,66 @@ describe('UserOperationController', () => { }); }); }); + + describe('metadata', () => { + it('includes expected state in debug snapshots', () => { + const controller = new UserOperationController(optionsMock); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'anonymous', + ), + ).toMatchInlineSnapshot(`Object {}`); + }); + + it('includes expected state in state logs', () => { + const controller = new UserOperationController(optionsMock); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + Object { + "userOperations": Object {}, + } + `); + }); + + it('persists expected state', () => { + const controller = new UserOperationController(optionsMock); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + Object { + "userOperations": Object {}, + } + `); + }); + + it('exposes expected state to UI', () => { + const controller = new UserOperationController(optionsMock); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + Object { + "userOperations": Object {}, + } + `); + }); + }); }); diff --git a/packages/user-operation-controller/src/UserOperationController.ts b/packages/user-operation-controller/src/UserOperationController.ts index 4dca2da946d..6f0180d5b84 100644 --- a/packages/user-operation-controller/src/UserOperationController.ts +++ b/packages/user-operation-controller/src/UserOperationController.ts @@ -57,7 +57,12 @@ import { const controllerName = 'UserOperationController'; const stateMetadata = { - userOperations: { persist: true, anonymous: false }, + userOperations: { + includeInStateLogs: true, + persist: true, + anonymous: false, + usedInUi: true, + }, }; const getDefaultState = () => ({ From ee776cf8dfff3841bc78bdd1ed347e3b7aea7c34 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 4 Sep 2025 17:59:33 -0230 Subject: [PATCH 2/3] Update changelogs --- packages/address-book-controller/CHANGELOG.md | 4 ++++ packages/approval-controller/CHANGELOG.md | 4 ++++ packages/ens-controller/CHANGELOG.md | 4 ++++ packages/gas-fee-controller/CHANGELOG.md | 4 ++++ packages/logging-controller/CHANGELOG.md | 4 ++++ packages/message-manager/CHANGELOG.md | 4 ++++ packages/name-controller/CHANGELOG.md | 4 ++++ packages/signature-controller/CHANGELOG.md | 4 ++++ packages/transaction-controller/CHANGELOG.md | 4 ++++ packages/user-operation-controller/CHANGELOG.md | 4 ++++ 10 files changed, 40 insertions(+) diff --git a/packages/address-book-controller/CHANGELOG.md b/packages/address-book-controller/CHANGELOG.md index 1b2fd9a9550..3d37b88854a 100644 --- a/packages/address-book-controller/CHANGELOG.md +++ b/packages/address-book-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/base-controller` from `^8.0.1` to `^8.3.0` ([#6284](https://github.com/MetaMask/core/pull/6284), [#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465)) diff --git a/packages/approval-controller/CHANGELOG.md b/packages/approval-controller/CHANGELOG.md index ec2f7f3d7bc..c4f4d1069f2 100644 --- a/packages/approval-controller/CHANGELOG.md +++ b/packages/approval-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/utils` from `^11.2.0` to `^11.4.2` ([#6054](https://github.com/MetaMask/core/pull/6054)) diff --git a/packages/ens-controller/CHANGELOG.md b/packages/ens-controller/CHANGELOG.md index 67e089d110c..5c1a0f96be0 100644 --- a/packages/ens-controller/CHANGELOG.md +++ b/packages/ens-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/base-controller` from `^8.0.1` to `^8.3.0` ([#6284](https://github.com/MetaMask/core/pull/6284), [#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465)) diff --git a/packages/gas-fee-controller/CHANGELOG.md b/packages/gas-fee-controller/CHANGELOG.md index 56ae7c2dc87..67662e04562 100644 --- a/packages/gas-fee-controller/CHANGELOG.md +++ b/packages/gas-fee-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/base-controller` from `^8.0.1` to `^8.3.0` ([#6284](https://github.com/MetaMask/core/pull/6284), [#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465)) diff --git a/packages/logging-controller/CHANGELOG.md b/packages/logging-controller/CHANGELOG.md index 25d8d52687e..189a126426f 100644 --- a/packages/logging-controller/CHANGELOG.md +++ b/packages/logging-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/base-controller` from `^8.0.0` to `^8.3.0` ([#5722](https://github.com/MetaMask/core/pull/5722), [#6284](https://github.com/MetaMask/core/pull/6284), [#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465)) diff --git a/packages/message-manager/CHANGELOG.md b/packages/message-manager/CHANGELOG.md index 34ddd65318c..91d48be4628 100644 --- a/packages/message-manager/CHANGELOG.md +++ b/packages/message-manager/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - **BREAKING:** `AbstractMessageManager` now expects a `Name extends string` generic parameter to define the name of the message manager ([#6469](https://github.com/MetaMask/core/pull/6469)) diff --git a/packages/name-controller/CHANGELOG.md b/packages/name-controller/CHANGELOG.md index ef2d1be208c..70b44984aa4 100644 --- a/packages/name-controller/CHANGELOG.md +++ b/packages/name-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/utils` from `^11.2.0` to `^11.4.2` ([#6054](https://github.com/MetaMask/core/pull/6054)) diff --git a/packages/signature-controller/CHANGELOG.md b/packages/signature-controller/CHANGELOG.md index adfd9ea2bc5..7ec76db42ee 100644 --- a/packages/signature-controller/CHANGELOG.md +++ b/packages/signature-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/base-controller` from `^8.1.0` to `^8.3.0` ([#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465)) diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 337382da6f8..f863ecc3fef 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Update nonce of existing transaction if converted to batch via `batchTransactions` but not first transaction ([#6528](https://github.com/MetaMask/core/pull/6528)) diff --git a/packages/user-operation-controller/CHANGELOG.md b/packages/user-operation-controller/CHANGELOG.md index da546a6b298..86a1dd63a4a 100644 --- a/packages/user-operation-controller/CHANGELOG.md +++ b/packages/user-operation-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6473](https://github.com/MetaMask/core/pull/6473)) + ### Changed - Bump `@metamask/base-controller` from `^8.1.0` to `^8.3.0` ([#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465)) From 5e4b883a1fa72b0eb6365f9a1118b58630c4b795 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 5 Sep 2025 14:37:07 -0230 Subject: [PATCH 3/3] Fix lint error --- .../src/UserOperationController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/user-operation-controller/src/UserOperationController.test.ts b/packages/user-operation-controller/src/UserOperationController.test.ts index 9868e09cd56..b1c26ff44a1 100644 --- a/packages/user-operation-controller/src/UserOperationController.test.ts +++ b/packages/user-operation-controller/src/UserOperationController.test.ts @@ -1,3 +1,4 @@ +import { deriveStateFromMetadata } from '@metamask/base-controller'; import { ApprovalType } from '@metamask/controller-utils'; import { errorCodes } from '@metamask/rpc-errors'; import { @@ -34,7 +35,6 @@ import { validateSignUserOperationResponse, validateUpdateUserOperationResponse, } from './utils/validation'; -import { deriveStateFromMetadata } from '@metamask/base-controller'; jest.mock('@metamask/transaction-controller'); jest.mock('./utils/gas');