From 477cc27d899942e1fe39ed2f76ce6b1d112a44a8 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:29:38 +1300 Subject: [PATCH 1/3] Remove storage from DelegationController --- packages/delegation-controller/package.json | 1 - ...elegationController-method-action-types.ts | 63 +- .../src/DelegationController.test.ts | 575 +----------------- .../src/DelegationController.ts | 186 +----- packages/delegation-controller/src/index.ts | 11 +- packages/delegation-controller/src/types.ts | 26 +- packages/delegation-controller/src/utils.ts | 13 +- yarn.lock | 1 - 8 files changed, 16 insertions(+), 860 deletions(-) diff --git a/packages/delegation-controller/package.json b/packages/delegation-controller/package.json index 49757a94a4c..3add94d524c 100644 --- a/packages/delegation-controller/package.json +++ b/packages/delegation-controller/package.json @@ -48,7 +48,6 @@ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" }, "dependencies": { - "@metamask/accounts-controller": "^37.1.1", "@metamask/base-controller": "^9.0.1", "@metamask/keyring-controller": "^25.1.1", "@metamask/messenger": "^1.0.0", diff --git a/packages/delegation-controller/src/DelegationController-method-action-types.ts b/packages/delegation-controller/src/DelegationController-method-action-types.ts index b108808b51c..1a9aef56e27 100644 --- a/packages/delegation-controller/src/DelegationController-method-action-types.ts +++ b/packages/delegation-controller/src/DelegationController-method-action-types.ts @@ -18,69 +18,8 @@ export type DelegationControllerSignDelegationAction = { handler: DelegationController['signDelegation']; }; -/** - * Stores a delegation in storage. - * - * @param params - The parameters for storing the delegation. - * @param params.entry - The delegation entry to store. - */ -export type DelegationControllerStoreAction = { - type: `DelegationController:store`; - handler: DelegationController['store']; -}; - -/** - * Lists delegation entries. - * - * @param filter - The filter to use to list the delegation entries. - * @returns A list of delegation entries that match the filter. - */ -export type DelegationControllerListAction = { - type: `DelegationController:list`; - handler: DelegationController['list']; -}; - -/** - * Retrieves the delegation entry for a given delegation hash. - * - * @param hash - The hash of the delegation to retrieve. - * @returns The delegation entry, or null if not found. - */ -export type DelegationControllerRetrieveAction = { - type: `DelegationController:retrieve`; - handler: DelegationController['retrieve']; -}; - -/** - * Retrieves a delegation chain from a delegation hash. - * - * @param hash - The hash of the delegation to retrieve. - * @returns The delegation chain, or null if not found. - */ -export type DelegationControllerChainAction = { - type: `DelegationController:chain`; - handler: DelegationController['chain']; -}; - -/** - * Deletes a delegation entrie from storage, along with any other entries - * that are redelegated from it. - * - * @param hash - The hash of the delegation to delete. - * @returns The number of entries deleted. - */ -export type DelegationControllerDeleteAction = { - type: `DelegationController:delete`; - handler: DelegationController['delete']; -}; - /** * Union of all DelegationController action types. */ export type DelegationControllerMethodActions = - | DelegationControllerSignDelegationAction - | DelegationControllerStoreAction - | DelegationControllerListAction - | DelegationControllerRetrieveAction - | DelegationControllerChainAction - | DelegationControllerDeleteAction; + DelegationControllerSignDelegationAction; diff --git a/packages/delegation-controller/src/DelegationController.test.ts b/packages/delegation-controller/src/DelegationController.test.ts index 9ab505410c5..5e885b7ed36 100644 --- a/packages/delegation-controller/src/DelegationController.test.ts +++ b/packages/delegation-controller/src/DelegationController.test.ts @@ -15,7 +15,6 @@ import type { Delegation, DelegationControllerMessenger, DelegationControllerState, - DelegationEntry, DeleGatorEnvironment, Hex, } from './types'; @@ -56,12 +55,6 @@ const DELEGATION_MOCK: Delegation = { signature: '0x', }; -const DELEGATION_ENTRY_MOCK: DelegationEntry = { - delegation: DELEGATION_MOCK, - chainId: CHAIN_ID_MOCK, // sepolia - tags: [], -}; - class TestDelegationController extends DelegationController { public testUpdate(updater: (state: DelegationControllerState) => void) { this.update(updater); @@ -78,19 +71,10 @@ function createMessengerMock() { namespace: MOCK_ANY_NAMESPACE, }); - const accountsControllerGetSelectedAccountMock = jest.fn(); const keyringControllerSignTypedMessageMock = jest.fn(); - accountsControllerGetSelectedAccountMock.mockReturnValue({ - address: FROM_MOCK, - }); - keyringControllerSignTypedMessageMock.mockResolvedValue(SIGNATURE_HASH_MOCK); - messenger.registerActionHandler( - 'AccountsController:getSelectedAccount', - accountsControllerGetSelectedAccountMock, - ); messenger.registerActionHandler( 'KeyringController:signTypedMessage', keyringControllerSignTypedMessageMock, @@ -107,31 +91,16 @@ function createMessengerMock() { }); messenger.delegate({ messenger: delegationControllerMessenger, - actions: [ - 'AccountsController:getSelectedAccount', - 'KeyringController:signTypedMessage', - ], + actions: ['KeyringController:signTypedMessage'], }); return { - accountsControllerGetSelectedAccountMock, keyringControllerSignTypedMessageMock, messenger: delegationControllerMessenger, rootMessenger: messenger, }; } -/** - * - * @param delegation - The delegation to hash. - * @returns The mock hash of the delegation (not real hash) - */ -function hashDelegationMock(delegation: Delegation): Hex { - return `0x${delegation.delegator.slice(2)}${delegation.delegate.slice( - 2, - )}${delegation.authority.slice(2)}${delegation.salt.slice(2)}`; -} - /** * Create a mock getDelegationEnvironment function. * @@ -159,7 +128,6 @@ function createController(state?: DelegationControllerState) { const controller = new TestDelegationController({ messenger, state, - hashDelegation: hashDelegationMock, getDelegationEnvironment: getDelegationEnvironmentMock, }); @@ -178,9 +146,7 @@ describe(`${controllerName}`, () => { describe('constructor', () => { it('initializes with default state', () => { const { controller } = createController(); - expect(controller.state).toStrictEqual({ - delegations: {}, - }); + expect(controller.state).toStrictEqual({}); }); }); @@ -236,537 +202,6 @@ describe(`${controllerName}`, () => { }); }); - describe('store', () => { - it('stores a delegation entry in state', () => { - const { controller, rootMessenger } = createController(); - const hash = hashDelegationMock(DELEGATION_ENTRY_MOCK.delegation); - - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - expect(controller.state.delegations[hash]).toStrictEqual( - DELEGATION_ENTRY_MOCK, - ); - }); - - it('overwrites existing delegation with same hash', () => { - const { controller, rootMessenger } = createController(); - const hash = hashDelegationMock(DELEGATION_ENTRY_MOCK.delegation); - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const updatedEntry = { - ...DELEGATION_ENTRY_MOCK, - tags: ['test-tag'], - }; - rootMessenger.call('DelegationController:store', { entry: updatedEntry }); - - expect(controller.state.delegations[hash]).toStrictEqual(updatedEntry); - }); - }); - - describe('list', () => { - it('lists all delegations for the requester as delegate', () => { - const { rootMessenger } = createController(); - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const result = rootMessenger.call('DelegationController:list'); - - expect(result).toHaveLength(1); - expect(result[0]).toStrictEqual(DELEGATION_ENTRY_MOCK); - }); - - it('filters delegations by from address', () => { - const { rootMessenger } = createController(); - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const result = rootMessenger.call('DelegationController:list', { - from: DELEGATION_MOCK.delegator, - }); - - expect(result).toHaveLength(1); - expect(result[0].delegation.delegator).toBe(DELEGATION_MOCK.delegator); - }); - - it('filters delegations by chainId', () => { - const { rootMessenger } = createController(); - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const result = rootMessenger.call('DelegationController:list', { - chainId: CHAIN_ID_MOCK, - }); - - expect(result).toHaveLength(1); - expect(result[0].chainId).toBe(CHAIN_ID_MOCK); - }); - - it('filters delegations by tags', () => { - const { rootMessenger } = createController(); - const entryWithTags = { - ...DELEGATION_ENTRY_MOCK, - tags: ['test-tag'], - }; - rootMessenger.call('DelegationController:store', { - entry: entryWithTags, - }); - - const result = rootMessenger.call('DelegationController:list', { - tags: ['test-tag'], - }); - - expect(result).toHaveLength(1); - expect(result[0].tags).toContain('test-tag'); - }); - - it('only filters entries that contain all of the filter tags', () => { - const { rootMessenger } = createController(); - const entryWithTags = { - ...DELEGATION_ENTRY_MOCK, - tags: ['test-tag', 'test-tag-1'], - }; - rootMessenger.call('DelegationController:store', { - entry: entryWithTags, - }); - - const result = rootMessenger.call('DelegationController:list', { - tags: ['test-tag', 'test-tag-2'], - }); - - expect(result).toHaveLength(0); - - const result2 = rootMessenger.call('DelegationController:list', { - tags: ['test-tag', 'test-tag-1'], - }); - expect(result2).toHaveLength(1); - expect(result2[0].tags).toContain('test-tag'); - expect(result2[0].tags).toContain('test-tag-1'); - }); - - it('combines multiple filters', () => { - const { rootMessenger } = createController(); - const entryWithTags = { - ...DELEGATION_ENTRY_MOCK, - tags: ['test-tag'], - }; - rootMessenger.call('DelegationController:store', { - entry: entryWithTags, - }); - - const result = rootMessenger.call('DelegationController:list', { - from: DELEGATION_MOCK.delegator, - chainId: CHAIN_ID_MOCK, - tags: ['test-tag'], - }); - - expect(result).toHaveLength(1); - expect(result[0].delegation.delegator).toBe(DELEGATION_MOCK.delegator); - expect(result[0].chainId).toBe(CHAIN_ID_MOCK); - expect(result[0].tags).toContain('test-tag'); - }); - - it('filters delegations by from address when requester is not the delegator', () => { - const { rootMessenger } = createController(); - const otherDelegation = { - ...DELEGATION_MOCK, - delegator: '0x9234567890123456789012345678901234567890' as Address, - }; - const otherEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: otherDelegation, - }; - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - rootMessenger.call('DelegationController:store', { entry: otherEntry }); - - const result = rootMessenger.call('DelegationController:list', { - from: otherDelegation.delegator, - }); - - expect(result).toHaveLength(1); - expect(result[0].delegation.delegator).toBe(otherDelegation.delegator); - }); - - it('filters delegations by from address when requester is the delegator', () => { - const { rootMessenger } = createController(); - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const result = rootMessenger.call('DelegationController:list', { - from: DELEGATION_MOCK.delegator, - }); - - expect(result).toHaveLength(1); - expect(result[0].delegation.delegator).toBe(DELEGATION_MOCK.delegator); - }); - - it('returns empty array when no delegations match filter', () => { - const { rootMessenger } = createController(); - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const result = rootMessenger.call('DelegationController:list', { - from: '0x9234567890123456789012345678901234567890' as Address, - chainId: CHAIN_ID_MOCK, - tags: ['non-existent-tag'], - }); - - expect(result).toHaveLength(0); - }); - }); - - describe('retrieve', () => { - it('retrieves delegation by hash', () => { - const { rootMessenger } = createController(); - const hash = hashDelegationMock(DELEGATION_ENTRY_MOCK.delegation); - - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const result = rootMessenger.call('DelegationController:retrieve', hash); - - expect(result).toStrictEqual(DELEGATION_ENTRY_MOCK); - }); - - it('returns null if hash not found', () => { - const { rootMessenger } = createController(); - - const result = rootMessenger.call( - 'DelegationController:retrieve', - '0x123' as Hex, - ); - - expect(result).toBeNull(); - }); - }); - - describe('chain', () => { - it('retrieves delegation chain from hash', () => { - const { rootMessenger } = createController(); - const parentDelegation = { - ...DELEGATION_MOCK, - authority: ROOT_AUTHORITY, - }; - const parentHash = hashDelegationMock(parentDelegation); - const childDelegation = { - ...DELEGATION_MOCK, - authority: parentHash, - }; - const childHash = hashDelegationMock(childDelegation); - const parentEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: parentDelegation, - }; - const childEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: childDelegation, - }; - rootMessenger.call('DelegationController:store', { entry: parentEntry }); - rootMessenger.call('DelegationController:store', { entry: childEntry }); - - const result = rootMessenger.call( - 'DelegationController:chain', - childHash, - ); - - expect(result).toHaveLength(2); - expect(result?.[0]).toStrictEqual(childEntry); - expect(result?.[1]).toStrictEqual(parentEntry); - }); - - it('returns null if hash not found', () => { - const { rootMessenger } = createController(); - - const result = rootMessenger.call( - 'DelegationController:chain', - '0x1234567890123456789012345678901234567890123456789012345678901234' as Hex, - ); - - expect(result).toBeNull(); - }); - - it('throws if delegation chain is invalid', () => { - const invalidDelegation = { - ...DELEGATION_MOCK, - authority: '0x123123123' as Hex, - }; - const invalidEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: invalidDelegation, - }; - const hash = hashDelegationMock(invalidEntry.delegation); - const invalidState = { - delegations: { - [hash]: invalidEntry, - }, - }; - const { rootMessenger } = createController(invalidState); - - expect(() => - rootMessenger.call('DelegationController:chain', hash), - ).toThrow('Invalid delegation chain'); - }); - - it('returns null for root authority', () => { - const { rootMessenger } = createController(); - const rootDelegation = { - ...DELEGATION_MOCK, - authority: ROOT_AUTHORITY, - }; - const rootEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: rootDelegation, - }; - rootMessenger.call('DelegationController:store', { entry: rootEntry }); - - const result = rootMessenger.call( - 'DelegationController:chain', - ROOT_AUTHORITY, - ); - - expect(result).toBeNull(); - }); - }); - - describe('delete', () => { - it('deletes delegation by hash', () => { - const { controller, rootMessenger } = createController(); - const hash = hashDelegationMock(DELEGATION_ENTRY_MOCK.delegation); - - rootMessenger.call('DelegationController:store', { - entry: DELEGATION_ENTRY_MOCK, - }); - - const count = rootMessenger.call('DelegationController:delete', hash); - - expect(count).toBe(1); - expect(controller.state.delegations[hash]).toBeUndefined(); - }); - - it('deletes delegation chain', () => { - const { controller, rootMessenger } = createController(); - const parentDelegation = { - ...DELEGATION_MOCK, - authority: ROOT_AUTHORITY, - }; - const parentHash = hashDelegationMock(parentDelegation); - const childDelegation = { - ...DELEGATION_MOCK, - authority: parentHash, - }; - const childHash = hashDelegationMock(childDelegation); - const parentEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: parentDelegation, - }; - const childEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: childDelegation, - }; - rootMessenger.call('DelegationController:store', { entry: parentEntry }); - rootMessenger.call('DelegationController:store', { entry: childEntry }); - - const count = rootMessenger.call( - 'DelegationController:delete', - parentHash, - ); - - expect(count).toBe(2); - expect(controller.state.delegations[childHash]).toBeUndefined(); - expect(controller.state.delegations[parentHash]).toBeUndefined(); - }); - - it('deletes delegation chain with multiple children', () => { - const { controller, rootMessenger } = createController(); - const parentDelegation = { - ...DELEGATION_MOCK, - authority: ROOT_AUTHORITY, - }; - const parentHash = hashDelegationMock(parentDelegation); - const child1Delegation = { - ...DELEGATION_MOCK, - authority: parentHash, - salt: '0x1' as Hex, - }; - const child1Hash = hashDelegationMock(child1Delegation); - const child2Delegation = { - ...DELEGATION_MOCK, - authority: parentHash, - salt: '0x2' as Hex, - }; - const child2Hash = hashDelegationMock(child2Delegation); - const parentEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: parentDelegation, - }; - const child1Entry = { - ...DELEGATION_ENTRY_MOCK, - delegation: child1Delegation, - }; - const child2Entry = { - ...DELEGATION_ENTRY_MOCK, - delegation: child2Delegation, - }; - rootMessenger.call('DelegationController:store', { entry: parentEntry }); - rootMessenger.call('DelegationController:store', { entry: child1Entry }); - rootMessenger.call('DelegationController:store', { entry: child2Entry }); - - const count = rootMessenger.call( - 'DelegationController:delete', - parentHash, - ); - - expect(count).toBe(3); - expect(controller.state.delegations[parentHash]).toBeUndefined(); - expect(controller.state.delegations[child1Hash]).toBeUndefined(); - expect(controller.state.delegations[child2Hash]).toBeUndefined(); - }); - - it('returns 0 when trying to delete non-existent delegation', () => { - const { rootMessenger } = createController(); - const count = rootMessenger.call( - 'DelegationController:delete', - '0x123' as Hex, - ); - expect(count).toBe(0); - }); - - it('deletes delegation with complex chain structure', () => { - const { controller, rootMessenger } = createController(); - // Create a chain: root -> parent -> child1 -> grandchild1 - // -> child2 -> grandchild2 - const rootDelegation = { - ...DELEGATION_MOCK, - authority: ROOT_AUTHORITY, - salt: '0x0' as Hex, - }; - const rootHash = hashDelegationMock(rootDelegation); - const parentDelegation = { - ...DELEGATION_MOCK, - authority: rootHash, - salt: '0x1' as Hex, - }; - const parentHash = hashDelegationMock(parentDelegation); - const child1Delegation = { - ...DELEGATION_MOCK, - authority: parentHash, - salt: '0x2' as Hex, - }; - const child1Hash = hashDelegationMock(child1Delegation); - const child2Delegation = { - ...DELEGATION_MOCK, - authority: parentHash, - salt: '0x3' as Hex, - }; - const child2Hash = hashDelegationMock(child2Delegation); - const grandchild1Delegation = { - ...DELEGATION_MOCK, - authority: child1Hash, - salt: '0x4' as Hex, - }; - const grandchild1Hash = hashDelegationMock(grandchild1Delegation); - const grandchild2Delegation = { - ...DELEGATION_MOCK, - authority: child2Hash, - salt: '0x5' as Hex, - }; - const grandchild2Hash = hashDelegationMock(grandchild2Delegation); - - const rootEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: rootDelegation, - }; - const parentEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: parentDelegation, - }; - const child1Entry = { - ...DELEGATION_ENTRY_MOCK, - delegation: child1Delegation, - }; - const child2Entry = { - ...DELEGATION_ENTRY_MOCK, - delegation: child2Delegation, - }; - const grandchild1Entry = { - ...DELEGATION_ENTRY_MOCK, - delegation: grandchild1Delegation, - }; - const grandchild2Entry = { - ...DELEGATION_ENTRY_MOCK, - delegation: grandchild2Delegation, - }; - - rootMessenger.call('DelegationController:store', { entry: rootEntry }); - rootMessenger.call('DelegationController:store', { entry: parentEntry }); - rootMessenger.call('DelegationController:store', { entry: child1Entry }); - rootMessenger.call('DelegationController:store', { entry: child2Entry }); - rootMessenger.call('DelegationController:store', { - entry: grandchild1Entry, - }); - rootMessenger.call('DelegationController:store', { - entry: grandchild2Entry, - }); - - const count = rootMessenger.call( - 'DelegationController:delete', - parentHash, - ); - - expect(count).toBe(5); // parent + 2 children + 2 grandchildren - expect(controller.state.delegations[rootHash]).toBeDefined(); - expect(controller.state.delegations[parentHash]).toBeUndefined(); - expect(controller.state.delegations[child1Hash]).toBeUndefined(); - expect(controller.state.delegations[child2Hash]).toBeUndefined(); - expect(controller.state.delegations[grandchild1Hash]).toBeUndefined(); - expect(controller.state.delegations[grandchild2Hash]).toBeUndefined(); - }); - - it('handles empty nextHashes array gracefully', () => { - const { controller, rootMessenger } = createController(); - // Mock the state to have an empty delegations object - controller.testUpdate((state) => { - state.delegations = {}; - }); - - // This should not throw and should return 0 - const count = rootMessenger.call( - 'DelegationController:delete', - '0x123' as Hex, - ); - expect(count).toBe(0); - }); - - it('throws if the authority is invalid', () => { - const { rootMessenger } = createController(); - const invalidDelegation = { - ...DELEGATION_MOCK, - authority: '0x1234567890123456789012345678901234567890' as Hex, - }; - const invalidEntry = { - ...DELEGATION_ENTRY_MOCK, - delegation: invalidDelegation, - }; - - expect(() => - rootMessenger.call('DelegationController:store', { - entry: invalidEntry, - }), - ).toThrow('Invalid authority'); - }); - }); - describe('metadata', () => { it('includes expected state in debug snapshots', () => { const { controller } = createController(); @@ -801,11 +236,7 @@ describe(`${controllerName}`, () => { controller.metadata, 'persist', ), - ).toMatchInlineSnapshot(` - { - "delegations": {}, - } - `); + ).toMatchInlineSnapshot(`{}`); }); it('includes expected state in UI', () => { diff --git a/packages/delegation-controller/src/DelegationController.ts b/packages/delegation-controller/src/DelegationController.ts index 38952d2aa78..98e399368ab 100644 --- a/packages/delegation-controller/src/DelegationController.ts +++ b/packages/delegation-controller/src/DelegationController.ts @@ -3,39 +3,21 @@ import { BaseController } from '@metamask/base-controller'; import { SignTypedDataVersion } from '@metamask/keyring-controller'; import { hexToNumber } from '@metamask/utils'; -import { ROOT_AUTHORITY } from './constants'; import type { - Address, - Delegation, DelegationControllerMessenger, DelegationControllerState, - DelegationEntry, - DelegationFilter, DeleGatorEnvironment, Hex, UnsignedDelegation, } from './types'; -import { createTypedMessageParams, isHexEqual } from './utils'; +import { createTypedMessageParams } from './utils'; export const controllerName = 'DelegationController'; -const MESSENGER_EXPOSED_METHODS = [ - 'signDelegation', - 'store', - 'list', - 'retrieve', - 'chain', - 'delete', -] as const; +const MESSENGER_EXPOSED_METHODS = ['signDelegation'] as const; -const delegationControllerMetadata = { - delegations: { - includeInStateLogs: false, - persist: true, - includeInDebugSnapshot: false, - usedInUi: false, - }, -} satisfies StateMetadata; +const delegationControllerMetadata = + {} satisfies StateMetadata; /** * Constructs the default {@link DelegationController} state. This allows @@ -46,22 +28,18 @@ const delegationControllerMetadata = { * @returns The default {@link DelegationController} state. */ function getDefaultDelegationControllerState(): DelegationControllerState { - return { - delegations: {}, - }; + return {}; } /** * The {@link DelegationController} class. - * This controller is meant to be a centralized place to store and sign delegations. + * This controller signs delegations via the keyring (typed-data signing). */ export class DelegationController extends BaseController< typeof controllerName, DelegationControllerState, DelegationControllerMessenger > { - readonly #hashDelegation: (delegation: Delegation) => Hex; - readonly #getDelegationEnvironment: (chainId: Hex) => DeleGatorEnvironment; /** @@ -70,18 +48,15 @@ export class DelegationController extends BaseController< * @param params - The parameters for constructing the controller. * @param params.messenger - The messenger instance to use for the controller. * @param params.state - The initial state for the controller. - * @param params.hashDelegation - A function to hash delegations. * @param params.getDelegationEnvironment - A function to get the delegation environment for a given chainId. */ constructor({ messenger, state, - hashDelegation, getDelegationEnvironment, }: { messenger: DelegationControllerMessenger; state?: Partial; - hashDelegation: (delegation: Delegation) => Hex; getDelegationEnvironment: (chainId: Hex) => DeleGatorEnvironment; }) { super({ @@ -93,7 +68,6 @@ export class DelegationController extends BaseController< ...state, }, }); - this.#hashDelegation = hashDelegation; this.#getDelegationEnvironment = getDelegationEnvironment; this.messenger.registerMethodActionHandlers( @@ -137,152 +111,4 @@ export class DelegationController extends BaseController< return signature; } - - /** - * Stores a delegation in storage. - * - * @param params - The parameters for storing the delegation. - * @param params.entry - The delegation entry to store. - */ - store(params: { entry: DelegationEntry }) { - const { entry } = params; - const hash = this.#hashDelegation(entry.delegation); - - // If the authority is not the root authority, validate that the - // parent entry does exist. - if ( - !isHexEqual(entry.delegation.authority, ROOT_AUTHORITY) && - !this.state.delegations[entry.delegation.authority] - ) { - throw new Error('Invalid authority'); - } - this.update((state) => { - state.delegations[hash] = entry; - }); - } - - /** - * Lists delegation entries. - * - * @param filter - The filter to use to list the delegation entries. - * @returns A list of delegation entries that match the filter. - */ - list(filter?: DelegationFilter) { - const account = this.messenger.call( - 'AccountsController:getSelectedAccount', - ); - const requester = account.address as Address; - - let list: DelegationEntry[] = Object.values(this.state.delegations); - - if (filter?.from) { - list = list.filter((entry) => - isHexEqual(entry.delegation.delegator, filter.from as Address), - ); - } - - if ( - !filter?.from || - (filter?.from && !isHexEqual(filter.from, requester)) - ) { - list = list.filter((entry) => - isHexEqual(entry.delegation.delegate, requester), - ); - } - - const filterChainId = filter?.chainId; - if (filterChainId) { - list = list.filter((entry) => isHexEqual(entry.chainId, filterChainId)); - } - - const tags = filter?.tags; - if (tags && tags.length > 0) { - // Filter entries that contain all of the filter tags - list = list.filter((entry) => - tags.every((tag) => entry.tags.includes(tag)), - ); - } - - return list; - } - - /** - * Retrieves the delegation entry for a given delegation hash. - * - * @param hash - The hash of the delegation to retrieve. - * @returns The delegation entry, or null if not found. - */ - retrieve(hash: Hex) { - return this.state.delegations[hash] ?? null; - } - - /** - * Retrieves a delegation chain from a delegation hash. - * - * @param hash - The hash of the delegation to retrieve. - * @returns The delegation chain, or null if not found. - */ - chain(hash: Hex) { - const chain: DelegationEntry[] = []; - - const entry = this.retrieve(hash); - if (!entry) { - return null; - } - chain.push(entry); - - for (let _hash = entry.delegation.authority; _hash !== ROOT_AUTHORITY; ) { - const parent = this.retrieve(_hash); - if (!parent) { - throw new Error('Invalid delegation chain'); - } - chain.push(parent); - _hash = parent.delegation.authority; - } - - return chain; - } - - /** - * Deletes a delegation entrie from storage, along with any other entries - * that are redelegated from it. - * - * @param hash - The hash of the delegation to delete. - * @returns The number of entries deleted. - */ - delete(hash: Hex): number { - const root = this.retrieve(hash); - if (!root) { - return 0; - } - - const entries = Object.entries(this.state.delegations); - const nextHashes: Hex[] = [hash]; - const deletedHashes: Hex[] = []; - - while (nextHashes.length > 0) { - const currentHash = nextHashes.pop() as Hex; - - // Find all delegations that have this hash as their authority - const children = entries.filter( - ([_, v]) => v.delegation.authority === currentHash, - ); - - // Add the hashes of all child delegations to be processed next - children.forEach(([k]) => { - nextHashes.push(k as Hex); - }); - - deletedHashes.push(currentHash); - } - - // Delete delegations - this.update((state) => { - deletedHashes.forEach((h) => { - delete state.delegations[h]; - }); - }); - - return deletedHashes.length; - } } diff --git a/packages/delegation-controller/src/index.ts b/packages/delegation-controller/src/index.ts index fd977f80220..493f3caaec6 100644 --- a/packages/delegation-controller/src/index.ts +++ b/packages/delegation-controller/src/index.ts @@ -1,18 +1,9 @@ -export type { - DelegationControllerSignDelegationAction, - DelegationControllerStoreAction, - DelegationControllerListAction, - DelegationControllerRetrieveAction, - DelegationControllerChainAction, - DelegationControllerDeleteAction, -} from './DelegationController-method-action-types'; +export type { DelegationControllerSignDelegationAction } from './DelegationController-method-action-types'; export type { DelegationControllerGetStateAction, DelegationControllerActions, DelegationControllerEvents, DelegationControllerMessenger, - DelegationEntry, - DelegationFilter, } from './types'; export { DelegationController } from './DelegationController'; diff --git a/packages/delegation-controller/src/types.ts b/packages/delegation-controller/src/types.ts index 3d154bf45aa..7566053570c 100644 --- a/packages/delegation-controller/src/types.ts +++ b/packages/delegation-controller/src/types.ts @@ -1,4 +1,3 @@ -import type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller'; import type { ControllerGetStateAction, ControllerStateChangeEvent, @@ -71,24 +70,9 @@ export type DelegationStruct = Omit & { salt: bigint; }; -export type DelegationEntry = { - tags: string[]; - chainId: Hex; - delegation: Delegation; - meta?: string; -}; - -export type DelegationFilter = { - chainId?: Hex; - tags?: string[]; - from?: Address; -}; - -export type DelegationControllerState = { - delegations: { - [hash: Hex]: DelegationEntry; - }; -}; +// Empty controller state (signing-only; no persisted fields). +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export type DelegationControllerState = {}; export type DelegationControllerGetStateAction = ControllerGetStateAction< typeof controllerName, @@ -106,9 +90,7 @@ export type DelegationControllerStateChangeEvent = ControllerStateChangeEvent< export type DelegationControllerEvents = DelegationControllerStateChangeEvent; -type AllowedActions = - | KeyringControllerSignTypedMessageAction - | AccountsControllerGetSelectedAccountAction; +type AllowedActions = KeyringControllerSignTypedMessageAction; type AllowedEvents = never; diff --git a/packages/delegation-controller/src/utils.ts b/packages/delegation-controller/src/utils.ts index e19d4dce64f..edefcd88ad8 100644 --- a/packages/delegation-controller/src/utils.ts +++ b/packages/delegation-controller/src/utils.ts @@ -2,18 +2,7 @@ import type { TypedMessageParams } from '@metamask/keyring-controller'; import { getChecksumAddress } from '@metamask/utils'; import { SIGNABLE_DELEGATION_TYPED_DATA } from './constants'; -import type { Address, Delegation, DelegationStruct, Hex } from './types'; - -/** - * Checks if two hex strings are equal. - * - * @param a - The first hex string. - * @param b - The second hex string. - * @returns True if the hex strings are equal, false otherwise. - */ -export function isHexEqual(a: Hex, b: Hex) { - return a.toLowerCase() === b.toLowerCase(); -} +import type { Address, Delegation, DelegationStruct } from './types'; type CreateTypedMessageParamsOptions = { chainId: number; diff --git a/yarn.lock b/yarn.lock index f2a8cce6114..c80c44b15ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3406,7 +3406,6 @@ __metadata: version: 0.0.0-use.local resolution: "@metamask/delegation-controller@workspace:packages/delegation-controller" dependencies: - "@metamask/accounts-controller": "npm:^37.1.1" "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^9.0.1" "@metamask/keyring-controller": "npm:^25.1.1" From 3828e8b9fcdfa81b32d796f07e53793b1d867666 Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:35:57 +1300 Subject: [PATCH 2/3] Update README and CHANGELOG to reflect changes --- packages/delegation-controller/CHANGELOG.md | 7 +++++-- packages/delegation-controller/README.md | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/delegation-controller/CHANGELOG.md b/packages/delegation-controller/CHANGELOG.md index c521d1dcc46..18414a31ccc 100644 --- a/packages/delegation-controller/CHANGELOG.md +++ b/packages/delegation-controller/CHANGELOG.md @@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Changed +### Removed -- Bump `@metamask/accounts-controller` from `^37.1.0` to `^37.1.1` ([#8325](https://github.com/MetaMask/core/pull/8325)) +- **BREAKING:** Remove persisted `delegations` state ([#8330](https://github.com/MetaMask/core/pull/8330)) + - `store`, `list`, `retrieve`, `chain`, and `delete` methods (and related messenger action types) + - `DelegationEntry` type export + - Remove dependency on `@metamask/accounts-controller` ## [2.1.0] diff --git a/packages/delegation-controller/README.md b/packages/delegation-controller/README.md index 2586a08c8cc..a35e4e97734 100644 --- a/packages/delegation-controller/README.md +++ b/packages/delegation-controller/README.md @@ -1,6 +1,6 @@ # `@metamask/delegation-controller` -Centralized place to store and sign delegations. +Signs delegations via the keyring using the Delegation Framework typed-data format. ## Installation From 435164ca70234b1dbc9bcf708f571c14f2ac077b Mon Sep 17 00:00:00 2001 From: Jeff Smale <6363749+jeffsmale90@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:23:40 +1300 Subject: [PATCH 3/3] Remove lint suppressions --- eslint-suppressions.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 215478769e8..4bbca620705 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -702,10 +702,7 @@ }, "packages/delegation-controller/src/DelegationController.ts": { "@typescript-eslint/explicit-function-return-type": { - "count": 5 - }, - "id-length": { - "count": 2 + "count": 1 } }, "packages/delegation-controller/src/types.ts": { @@ -713,11 +710,6 @@ "count": 3 } }, - "packages/delegation-controller/src/utils.ts": { - "@typescript-eslint/explicit-function-return-type": { - "count": 1 - } - }, "packages/earn-controller/src/EarnController.test.ts": { "@typescript-eslint/explicit-function-return-type": { "count": 1