Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
47935e0
Add signEIP7702Authorization to KeyringController and corresponding t…
jeffsmale90 Feb 3, 2025
276485e
Remove duplicated input validation
jeffsmale90 Feb 12, 2025
5e46b8d
Rename registered message name from ...EIP7702... to ...Eip7702...
jeffsmale90 Feb 12, 2025
df91d05
Update references to @metamask/utils to be consistently 11.2.0 across…
jeffsmale90 Feb 12, 2025
3f01228
Deduplicate dependencies with yarn dedupe
jeffsmale90 Feb 12, 2025
763cb3e
Merge branch 'main' into feat/7702-sign-authorization
jeffsmale90 Feb 12, 2025
b5e21fd
Move message params to KeyringController/types.ts, remove 'Message' f…
jeffsmale90 Feb 12, 2025
b96c9f7
Remove dependency on @metamask/message-manager from @metamask/keyring…
jeffsmale90 Feb 12, 2025
d1c2346
Update @metamask/utils reference in multichain-network-controller
jeffsmale90 Feb 12, 2025
a50fd44
Remove 'Message' from action name
jeffsmale90 Feb 13, 2025
73e18fe
Merge branch 'main' into feat/7702-sign-authorization
jeffsmale90 Feb 13, 2025
38f1570
Merge branch 'main' into feat/7702-sign-authorization
jeffsmale90 Feb 14, 2025
a348dca
Update bridge-controller dependency on @metamask/utils to 11.2.0
jeffsmale90 Feb 14, 2025
332cb55
Remove 'Message' from action type _name_
jeffsmale90 Feb 14, 2025
2394767
Update message type name to include "Eip7702"
jeffsmale90 Feb 14, 2025
a98db58
Remove obsolete keyring types definitions, add ts-expect-errors where…
jeffsmale90 Feb 14, 2025
c4f17a1
Fix linting issues introduced by ts-expect-error directives
jeffsmale90 Feb 14, 2025
b2b27cd
Merge branch 'main' into feat/7702-sign-authorization
ccharly Feb 14, 2025
21e65a0
Merge branch 'main' into feat/7702-sign-authorization
jeffsmale90 Feb 16, 2025
959414e
Merge branch 'main' into feat/7702-sign-authorization
jeffsmale90 Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/example-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/utils": "^11.1.0"
"@metamask/utils": "^11.2.0"
},
"devDependencies": {
"@metamask/auto-changelog": "^3.4.4",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@metamask/eth-block-tracker": "^11.0.3",
"@metamask/eth-json-rpc-provider": "^4.1.8",
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@ts-bridge/cli": "^0.6.1",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.191",
Expand Down
2 changes: 1 addition & 1 deletion packages/accounts-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@metamask/network-controller": "^22.2.1",
"@metamask/snaps-sdk": "^6.17.1",
"@metamask/snaps-utils": "^8.10.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"deepmerge": "^4.2.2",
"ethereum-cryptography": "^2.1.2",
"immer": "^9.0.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/address-book-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/utils": "^11.1.0"
"@metamask/utils": "^11.2.0"
},
"devDependencies": {
"@metamask/auto-changelog": "^3.4.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/approval-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"nanoid": "^3.3.8"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/assets-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@metamask/polling-controller": "^12.0.3",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/snaps-utils": "^8.10.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/bn.js": "^5.1.5",
"@types/uuid": "^8.3.0",
"async-mutex": "^0.5.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/base-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"dependencies": {
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"immer": "^9.0.6"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@metamask/controller-utils": "^11.5.0",
"@metamask/metamask-eth-abis": "^3.1.1",
"@metamask/polling-controller": "^12.0.3",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"ethers": "^6.12.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/build-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"dependencies": {
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/eslint": "^8.44.7"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/controller-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@ethereumjs/util": "^8.1.0",
"@metamask/eth-query": "^4.0.0",
"@metamask/ethjs-unit": "^0.3.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@spruceid/siwe-parser": "2.1.0",
"@types/bn.js": "^5.1.5",
"bignumber.js": "^9.1.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/ens-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@ethersproject/providers": "^5.7.0",
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"punycode": "^2.1.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/eth-json-rpc-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/safe-event-emitter": "^3.0.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"uuid": "^8.3.2"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/gas-fee-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@metamask/eth-query": "^4.0.0",
"@metamask/ethjs-unit": "^0.3.0",
"@metamask/polling-controller": "^12.0.3",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/bn.js": "^5.1.5",
"@types/uuid": "^8.3.0",
"bn.js": "^5.2.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/json-rpc-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"dependencies": {
"@metamask/rpc-errors": "^7.0.2",
"@metamask/safe-event-emitter": "^3.0.0",
"@metamask/utils": "^11.1.0"
"@metamask/utils": "^11.2.0"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^3.0.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/json-rpc-middleware-stream/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/safe-event-emitter": "^3.0.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"readable-stream": "^3.6.2"
},
"devDependencies": {
Expand Down
9 changes: 4 additions & 5 deletions packages/keyring-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,12 @@
"@keystonehq/metamask-airgapped-keyring": "^0.14.1",
"@metamask/base-controller": "^8.0.0",
"@metamask/browser-passworder": "^4.3.0",
"@metamask/eth-hd-keyring": "^7.0.4",
"@metamask/eth-sig-util": "^8.0.0",
"@metamask/eth-simple-keyring": "^6.0.5",
"@metamask/eth-hd-keyring": "^10.0.0",
"@metamask/eth-sig-util": "^8.2.0",
"@metamask/eth-simple-keyring": "^8.1.0",
"@metamask/keyring-api": "^17.0.0",
"@metamask/keyring-internal-api": "^4.0.1",
"@metamask/message-manager": "^12.0.1",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"async-mutex": "^0.5.0",
"ethereumjs-wallet": "^1.0.1",
"immer": "^9.0.6",
Expand Down
78 changes: 76 additions & 2 deletions packages/keyring-controller/src/KeyringController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {
recoverTypedSignature,
SignTypedDataVersion,
encrypt,
recoverEIP7702Authorization,
} from '@metamask/eth-sig-util';
import SimpleKeyring from '@metamask/eth-simple-keyring/dist/simple-keyring';
import SimpleKeyring from '@metamask/eth-simple-keyring';
import type { EthKeyring } from '@metamask/keyring-internal-api';
import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english';
import type { KeyringClass } from '@metamask/utils';
Expand Down Expand Up @@ -105,7 +106,8 @@ describe('KeyringController', () => {

it('allows overwriting the built-in Simple keyring builder', async () => {
const mockSimpleKeyringBuilder =
// @ts-expect-error The simple keyring doesn't yet conform to the KeyringClass type
// todo: keyring types are mismatched, this should be fixed in they keyrings themselves
// @ts-expect-error keyring types are mismatched
buildKeyringBuilderWithSpy(SimpleKeyring);
await withController(
{ keyringBuilders: [mockSimpleKeyringBuilder] },
Expand All @@ -118,6 +120,8 @@ describe('KeyringController', () => {
});

it('allows overwriting the built-in HD keyring builder', async () => {
// todo: keyring types are mismatched, this should be fixed in they keyrings themselves
// @ts-expect-error keyring types are mismatched
const mockHdKeyringBuilder = buildKeyringBuilderWithSpy(HDKeyring);
await withController(
{ keyringBuilders: [mockHdKeyringBuilder] },
Expand Down Expand Up @@ -621,6 +625,8 @@ describe('KeyringController', () => {
it('should throw error if the first account is not found on the keyring', async () => {
jest
.spyOn(HDKeyring.prototype, 'getAccounts')
// todo: keyring types are mismatched, this should be fixed in they keyrings themselves
// @ts-expect-error keyring types are mismatched
.mockResolvedValue([]);
await withController(
{ cacheEncryptionKey, skipVaultCreation: true },
Expand Down Expand Up @@ -1672,6 +1678,74 @@ describe('KeyringController', () => {
});
});

describe('signEip7702Authorization', () => {
const from = '0x5AC6D462f054690a373FABF8CC28e161003aEB19';
stubKeyringClassWithAccount(MockKeyring, from);
const chainId = 1;
const contractAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
const nonce = 1;

describe('when the keyring for the given address supports signEip7702Authorization', () => {
it('should sign EIP-7702 authorization message', async () => {
await withController(async ({ controller, initialState }) => {
const account = initialState.keyrings[0].accounts[0];
const signature = await controller.signEip7702Authorization({
from: account,
chainId,
contractAddress,
nonce,
});

const recovered = recoverEIP7702Authorization({
authorization: [chainId, contractAddress, nonce],
signature,
});

expect(recovered).toBe(account);
});
});

it('should not sign EIP-7702 authorization message if from account is not passed', async () => {
await withController(async ({ controller }) => {
await expect(
controller.signEip7702Authorization({
chainId,
contractAddress,
nonce,
from: '',
}),
).rejects.toThrow(
'KeyringController - No keyring found. Error info: There are keyrings, but none match the address',
);
});
});
});

describe('when the keyring for the given address does not support signEip7702Authorization', () => {
it('should throw error', async () => {
stubKeyringClassWithAccount(MockKeyring, from);

await withController(
{ keyringBuilders: [keyringBuilderFactory(MockKeyring)] },
async ({ controller }) => {
await controller.addNewKeyring(MockKeyring.type);

await expect(
controller.signEip7702Authorization({
from,
chainId,
contractAddress,
nonce,
}),
).rejects.toThrow(
KeyringControllerError.UnsupportedSignEip7702Authorization,
);
},
);
});
});
});

describe('signTypedMessage', () => {
describe('when the keyring for the given address supports signTypedMessage', () => {
it('should throw when given invalid version', async () => {
Expand Down
55 changes: 51 additions & 4 deletions packages/keyring-controller/src/KeyringController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ import type {
EthUserOperationPatch,
} from '@metamask/keyring-api';
import type { EthKeyring } from '@metamask/keyring-internal-api';
import type {
PersonalMessageParams,
TypedMessageParams,
} from '@metamask/message-manager';
import type {
Eip1024EncryptedData,
Hex,
Expand All @@ -47,6 +43,11 @@ import type { Patch } from 'immer';
import { ulid } from 'ulid';

import { KeyringControllerError } from './constants';
import type {
Eip7702AuthorizationParams,
PersonalMessageParams,
TypedMessageParams,
} from './types';

const name = 'KeyringController';

Expand Down Expand Up @@ -123,6 +124,11 @@ export type KeyringControllerSignMessageAction = {
handler: KeyringController['signMessage'];
};

export type KeyringControllerSignEip7702AuthorizationAction = {
type: `${typeof name}:signEip7702Authorization`;
handler: KeyringController['signEip7702Authorization'];
};

export type KeyringControllerSignPersonalMessageAction = {
type: `${typeof name}:signPersonalMessage`;
handler: KeyringController['signPersonalMessage'];
Expand Down Expand Up @@ -216,6 +222,7 @@ export type KeyringControllerQRKeyringStateChangeEvent = {
export type KeyringControllerActions =
| KeyringControllerGetStateAction
| KeyringControllerSignMessageAction
| KeyringControllerSignEip7702AuthorizationAction
| KeyringControllerSignPersonalMessageAction
| KeyringControllerSignTypedMessageAction
| KeyringControllerDecryptMessageAction
Expand Down Expand Up @@ -452,7 +459,10 @@ export function keyringBuilderFactory(KeyringConstructor: KeyringClass<Json>) {
}

const defaultKeyringBuilders = [
// todo: keyring types are mismatched, this should be fixed in they keyrings themselves
// @ts-expect-error keyring types are mismatched
keyringBuilderFactory(SimpleKeyring),
// @ts-expect-error keyring types are mismatched
keyringBuilderFactory(HDKeyring),
];

Expand Down Expand Up @@ -1182,6 +1192,38 @@ export class KeyringController extends BaseController<
return await keyring.signMessage(address, messageParams.data);
}

/**
* Signs EIP-7702 Authorization message by calling down into a specific keyring.
*
* @param params - EIP7702AuthorizationParams object to sign.
* @returns Promise resolving to an EIP-7702 Authorization signature.
* @throws Will throw UnsupportedSignEIP7702Authorization if the keyring does not support signing EIP-7702 Authorization messages.
*/
async signEip7702Authorization(
params: Eip7702AuthorizationParams,
): Promise<string> {
const from = ethNormalize(params.from) as Hex;

const keyring = (await this.getKeyringForAccount(from)) as EthKeyring<Json>;

if (!keyring.signEip7702Authorization) {
throw new Error(
KeyringControllerError.UnsupportedSignEip7702Authorization,
);
}

const { chainId, nonce } = params;
const contractAddress = ethNormalize(params.contractAddress) as
| Hex
| undefined;

return await keyring.signEip7702Authorization(from, [
chainId,
contractAddress as Hex,
Copy link
Member

@Gudahtt Gudahtt Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This cast is suppressing a TypeScript error about the undefined case. We asserted just a couple lines above that this value can be undefined. Could you clarify why we're overriding that error here? Is the address expected to be undefined in some cases or not?

Edit: I think I see what happened now, suggestion on this here: https://github.com/MetaMask/core/pull/5301/files#r1958425721

Comment on lines +1216 to +1222
Copy link
Member

@Gudahtt Gudahtt Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Perhaps this is what you meant to do:

Suggested change
const contractAddress = ethNormalize(params.contractAddress) as
| Hex
| undefined;
return await keyring.signEip7702Authorization(from, [
chainId,
contractAddress as Hex,
const contractAddress = ethNormalize(params.contractAddress) as Hex;
return await keyring.signEip7702Authorization(from, [
chainId,
contractAddress,

Ideally we'd validate the response from ethNormalize is non-falsy as well, or use a normalization function that returns Hex rather than string | undefined. But I see we make this assumption elsewhere already, so we can leave this improvement for a future PR.

nonce,
]);
}

/**
* Signs personal message by calling down into a specific keyring.
*
Expand Down Expand Up @@ -1795,6 +1837,11 @@ export class KeyringController extends BaseController<
this.signMessage.bind(this),
);

this.messagingSystem.registerActionHandler(
`${name}:signEip7702Authorization`,
this.signEip7702Authorization.bind(this),
);

this.messagingSystem.registerActionHandler(
`${name}:signPersonalMessage`,
this.signPersonalMessage.bind(this),
Expand Down
1 change: 1 addition & 0 deletions packages/keyring-controller/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum KeyringControllerError {
UnsupportedSignTransaction = 'KeyringController - The keyring for the current address does not support the method signTransaction.',
UnsupportedSignMessage = 'KeyringController - The keyring for the current address does not support the method signMessage.',
UnsupportedSignPersonalMessage = 'KeyringController - The keyring for the current address does not support the method signPersonalMessage.',
UnsupportedSignEip7702Authorization = 'KeyringController - The keyring for the current address does not support the method signEip7702Authorization.',
UnsupportedGetEncryptionPublicKey = 'KeyringController - The keyring for the current address does not support the method getEncryptionPublicKey.',
UnsupportedDecryptMessage = 'KeyringController - The keyring for the current address does not support the method decryptMessage.',
UnsupportedSignTypedMessage = 'KeyringController - The keyring for the current address does not support the method signTypedMessage.',
Expand Down
1 change: 1 addition & 0 deletions packages/keyring-controller/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './KeyringController';
export type * from './types';
Loading
Loading