diff --git a/src/SnapKeyring.test.ts b/src/SnapKeyring.test.ts index dbc731a9..495dec3c 100644 --- a/src/SnapKeyring.test.ts +++ b/src/SnapKeyring.test.ts @@ -5,11 +5,13 @@ import type { EthUserOperation, EthUserOperationPatch, KeyringAccount, + KeyringExecutionContext, } from '@metamask/keyring-api'; import { EthAccountType, EthMethod } from '@metamask/keyring-api'; import { KeyringEvent } from '@metamask/keyring-api/dist/events'; import type { SnapController } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; +import { KnownCaipNamespace, toCaipChainId } from '@metamask/utils'; import type { KeyringState } from '.'; import { SnapKeyring } from '.'; @@ -59,6 +61,10 @@ describe('SnapKeyring', () => { }, ] as const; + const executionContext: KeyringExecutionContext = { + chainId: '1', + }; + beforeEach(async () => { keyring = new SnapKeyring( mockSnapController as unknown as SnapController, @@ -908,6 +914,7 @@ describe('SnapKeyring', () => { const baseUserOp = await keyring.prepareUserOperation( accounts[0].address, baseTxs, + executionContext, ); expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ @@ -920,7 +927,10 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: expect.any(String), + scope: toCaipChainId( + KnownCaipNamespace.Eip155, + executionContext.chainId, + ), account: accounts[0].id, request: { method: 'eth_prepareUserOperation', @@ -960,6 +970,7 @@ describe('SnapKeyring', () => { const patch = await keyring.patchUserOperation( accounts[0].address, userOp, + executionContext, ); expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ @@ -972,7 +983,10 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: expect.any(String), + scope: toCaipChainId( + KnownCaipNamespace.Eip155, + executionContext.chainId, + ), account: accounts[0].id, request: { method: 'eth_patchUserOperation', @@ -1008,6 +1022,7 @@ describe('SnapKeyring', () => { const signature = await keyring.signUserOperation( accounts[0].address, userOp, + executionContext, ); expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ @@ -1020,7 +1035,10 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: expect.any(String), + scope: toCaipChainId( + KnownCaipNamespace.Eip155, + executionContext.chainId, + ), account: accounts[0].id, request: { method: 'eth_signUserOperation', @@ -1220,7 +1238,11 @@ describe('SnapKeyring', () => { result: mockExpectedUserOp, }); - await keyring.prepareUserOperation(accounts[0].address, mockIntents); + await keyring.prepareUserOperation( + accounts[0].address, + mockIntents, + executionContext, + ); expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ handler: 'onKeyringRequest', @@ -1231,7 +1253,10 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: expect.any(String), + scope: toCaipChainId( + KnownCaipNamespace.Eip155, + executionContext.chainId, + ), account: accounts[0].id, request: { method: 'eth_prepareUserOperation', @@ -1249,7 +1274,11 @@ describe('SnapKeyring', () => { }); await expect( - keyring.prepareUserOperation(accounts[0].address, mockIntents), + keyring.prepareUserOperation( + accounts[0].address, + mockIntents, + executionContext, + ), ).rejects.toThrow(regexForUUIDInRequiredSyncErrorMessage); }); }); @@ -1279,7 +1308,11 @@ describe('SnapKeyring', () => { result: mockExpectedPatch, }); - await keyring.patchUserOperation(accounts[0].address, mockUserOp); + await keyring.patchUserOperation( + accounts[0].address, + mockUserOp, + executionContext, + ); expect(mockSnapController.handleRequest).toHaveBeenCalledWith({ handler: 'onKeyringRequest', @@ -1290,7 +1323,10 @@ describe('SnapKeyring', () => { method: 'keyring_submitRequest', params: { id: expect.any(String), - scope: expect.any(String), + scope: toCaipChainId( + KnownCaipNamespace.Eip155, + executionContext.chainId, + ), account: accounts[0].id, request: { method: 'eth_patchUserOperation', @@ -1308,7 +1344,11 @@ describe('SnapKeyring', () => { }); await expect( - keyring.patchUserOperation(accounts[0].address, mockUserOp), + keyring.patchUserOperation( + accounts[0].address, + mockUserOp, + executionContext, + ), ).rejects.toThrow(regexForUUIDInRequiredSyncErrorMessage); }); }); diff --git a/src/SnapKeyring.ts b/src/SnapKeyring.ts index 0e0347d5..480a47c3 100644 --- a/src/SnapKeyring.ts +++ b/src/SnapKeyring.ts @@ -10,6 +10,7 @@ import type { InternalAccount, KeyringAccount, KeyringResponse, + KeyringExecutionContext, } from '@metamask/keyring-api'; import { AccountCreatedEventStruct, @@ -27,12 +28,15 @@ import type { SnapController } from '@metamask/snaps-controllers'; import type { SnapId } from '@metamask/snaps-sdk'; import type { Snap } from '@metamask/snaps-utils'; import type { Json } from '@metamask/utils'; -import { bigIntToHex } from '@metamask/utils'; +import { + bigIntToHex, + toCaipChainId, + KnownCaipNamespace, +} from '@metamask/utils'; import { EventEmitter } from 'events'; import { assert, mask, object, string } from 'superstruct'; import { v4 as uuid } from 'uuid'; -import { toCaipChainId, CaipNamespaces } from './caip'; import { DeferredPromise } from './DeferredPromise'; import { KeyringSnapControllerClient } from './KeyringSnapControllerClient'; import { projectLogger as log } from './logger'; @@ -609,7 +613,7 @@ export class SnapKeyring extends EventEmitter { address, method: EthMethod.SignTransaction, params: [tx], - chainId: toCaipChainId(CaipNamespaces.Eip155, `${chainId}`), + chainId: toCaipChainId(KnownCaipNamespace.Eip155, `${chainId}`), }); // ! It's *** CRITICAL *** that we mask the signature here, otherwise the @@ -665,7 +669,9 @@ export class SnapKeyring extends EventEmitter { params: toJson([address, data]), ...(chainId === undefined ? {} - : { chainId: toCaipChainId(CaipNamespaces.Eip155, `${chainId}`) }), + : { + chainId: toCaipChainId(KnownCaipNamespace.Eip155, `${chainId}`), + }), }), EthBytesStruct, ); @@ -715,11 +721,13 @@ export class SnapKeyring extends EventEmitter { * * @param address - Address of the sender. * @param transactions - Base transactions to include in the UserOperation. + * @param context - Keyring execution context. * @returns A pseudo-UserOperation that can be used to construct a real. */ async prepareUserOperation( address: string, transactions: EthBaseTransaction[], + context: KeyringExecutionContext, ): Promise { return strictMask( await this.#submitRequest({ @@ -727,6 +735,8 @@ export class SnapKeyring extends EventEmitter { method: EthMethod.PrepareUserOperation, params: toJson(transactions), expectSync: true, + // We assume the chain ID is already well formatted + chainId: toCaipChainId(KnownCaipNamespace.Eip155, context.chainId), }), EthBaseUserOperationStruct, ); @@ -738,11 +748,13 @@ export class SnapKeyring extends EventEmitter { * * @param address - Address of the sender. * @param userOp - UserOperation to patch. + * @param context - Keyring execution context. * @returns A patch to apply to the UserOperation. */ async patchUserOperation( address: string, userOp: EthUserOperation, + context: KeyringExecutionContext, ): Promise { return strictMask( await this.#submitRequest({ @@ -750,6 +762,8 @@ export class SnapKeyring extends EventEmitter { method: EthMethod.PatchUserOperation, params: toJson([userOp]), expectSync: true, + // We assume the chain ID is already well formatted + chainId: toCaipChainId(KnownCaipNamespace.Eip155, context.chainId), }), EthUserOperationPatchStruct, ); @@ -760,17 +774,21 @@ export class SnapKeyring extends EventEmitter { * * @param address - Address of the sender. * @param userOp - UserOperation to sign. + * @param context - Leyring execution context. * @returns The signature of the UserOperation. */ async signUserOperation( address: string, userOp: EthUserOperation, + context: KeyringExecutionContext, ): Promise { return strictMask( await this.#submitRequest({ address, method: EthMethod.SignUserOperation, params: toJson([userOp]), + // We assume the chain ID is already well formatted + chainId: toCaipChainId(KnownCaipNamespace.Eip155, context.chainId), }), EthBytesStruct, ); diff --git a/src/caip.test.ts b/src/caip.test.ts deleted file mode 100644 index 8da4bbd4..00000000 --- a/src/caip.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CAIP_NAMESPACE_REGEX, CAIP_REFERENCE_REGEX } from '@metamask/utils'; - -import { toCaipChainId, CaipNamespaces } from './caip'; - -describe('toCaipChainId', () => { - // This function relies on @metamask/utils CAIP helpers. Those are being - // tested with a variety of inputs. - // Here we mainly focus on our own wrapper around those: - - it('returns a valid CAIP-2 chain ID', () => { - const namespace = CaipNamespaces.Eip155; - const reference = '1'; - expect(toCaipChainId(namespace, reference)).toBe( - `${namespace}:${reference}`, - ); - }); - - it.each([ - // Too short, must have 3 chars at least - '', - 'xs', - // Not matching - '!@#$%^&*()', - // Too long - 'namespacetoolong', - ])('throws for invalid namespaces: %s', (namespace) => { - const reference = '1'; - expect(() => toCaipChainId(namespace, reference)).toThrow( - `Invalid "namespace", must match: ${CAIP_NAMESPACE_REGEX.toString()}`, - ); - }); - - it.each([ - // Too short, must have 1 char at least - '', - // Not matching - '!@#$%^&*()', - // Too long - '012345678901234567890123456789012', // 33 chars - ])('throws for invalid reference: %s', (reference) => { - const namespace = CaipNamespaces.Eip155; - expect(() => toCaipChainId(namespace, reference)).toThrow( - `Invalid "reference", must match: ${CAIP_REFERENCE_REGEX.toString()}`, - ); - }); -}); diff --git a/src/caip.ts b/src/caip.ts deleted file mode 100644 index 127664f2..00000000 --- a/src/caip.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - CAIP_NAMESPACE_REGEX, - CAIP_REFERENCE_REGEX, - isCaipNamespace, - isCaipReference, -} from '@metamask/utils'; -import type { - CaipNamespace, - CaipReference, - CaipChainId, -} from '@metamask/utils'; - -/** Supported CAIP namespaces. */ -export const CaipNamespaces = { - /** Namespace for EIP-155 compatible chains. */ - Eip155: 'eip155' as CaipNamespace, -} as const; - -/** - * Chain ID as defined per the CAIP-2 - * {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md}. - * - * It defines a way to uniquely identify any blockchain in a human-readable - * way. - * - * @param namespace - The standard (ecosystem) of similar blockchains. - * @param reference - Identify of a blockchain within a given namespace. - * @throws {@link Error} - * This exception is thrown if the inputs does not comply with the CAIP-2 - * syntax specification - * {@link https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md#syntax}. - * @returns A CAIP chain ID. - */ -export function toCaipChainId( - namespace: CaipNamespace, - reference: CaipReference, -): CaipChainId { - if (!isCaipNamespace(namespace)) { - throw new Error( - `Invalid "namespace", must match: ${CAIP_NAMESPACE_REGEX.toString()}`, - ); - } - - if (!isCaipReference(reference)) { - throw new Error( - `Invalid "reference", must match: ${CAIP_REFERENCE_REGEX.toString()}`, - ); - } - - return `${namespace}:${reference}`; -}