diff --git a/packages/examples/packages/ethereum-provider/src/index.test.ts b/packages/examples/packages/ethereum-provider/src/index.test.ts index e729884692..a73ddb1adf 100644 --- a/packages/examples/packages/ethereum-provider/src/index.test.ts +++ b/packages/examples/packages/ethereum-provider/src/index.test.ts @@ -68,4 +68,21 @@ describe('onRpcRequest', () => { await close(); }); }); + + describe('getAccounts', () => { + it('returns the addresses granted access to by the user', async () => { + const { request, close } = await installSnap(); + + const response = await request({ + method: 'getAccounts', + }); + + // Currently, snaps-jest will always return this account. + expect(response).toRespondWith([ + '0xc6d5a3c98ec9073b54fa0969957bd582e8d874bf', + ]); + + await close(); + }); + }); }); diff --git a/packages/snaps-simulator/src/features/simulation/middleware.test.ts b/packages/snaps-simulator/src/features/simulation/middleware.test.ts index 893caa08a0..3e3e1078ab 100644 --- a/packages/snaps-simulator/src/features/simulation/middleware.test.ts +++ b/packages/snaps-simulator/src/features/simulation/middleware.test.ts @@ -1,11 +1,15 @@ +import { mnemonicPhraseToBytes } from '@metamask/key-tree'; import { JsonRpcEngine } from 'json-rpc-engine'; +import { DEFAULT_SRP } from '../configuration'; import { createMiscMethodMiddleware } from './middleware'; +const hooks = { getMnemonic: async () => mnemonicPhraseToBytes(DEFAULT_SRP) }; + describe('createMiscMethodMiddleware', () => { it('supports metamask_getProviderState', async () => { const engine = new JsonRpcEngine(); - engine.push(createMiscMethodMiddleware()); + engine.push(createMiscMethodMiddleware(hooks)); const response = await engine.handle({ jsonrpc: '2.0', @@ -25,4 +29,40 @@ describe('createMiscMethodMiddleware', () => { }, }); }); + + it('supports eth_accounts', async () => { + const engine = new JsonRpcEngine(); + engine.push(createMiscMethodMiddleware(hooks)); + + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'eth_accounts', + params: [], + }); + + expect(response).toStrictEqual({ + id: 1, + jsonrpc: '2.0', + result: ['0xc6d5a3c98ec9073b54fa0969957bd582e8d874bf'], + }); + }); + + it('supports eth_requestAccounts', async () => { + const engine = new JsonRpcEngine(); + engine.push(createMiscMethodMiddleware(hooks)); + + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'eth_requestAccounts', + params: [], + }); + + expect(response).toStrictEqual({ + id: 1, + jsonrpc: '2.0', + result: ['0xc6d5a3c98ec9073b54fa0969957bd582e8d874bf'], + }); + }); }); diff --git a/packages/snaps-simulator/src/features/simulation/middleware.ts b/packages/snaps-simulator/src/features/simulation/middleware.ts index 219dad762c..0b6ff69efa 100644 --- a/packages/snaps-simulator/src/features/simulation/middleware.ts +++ b/packages/snaps-simulator/src/features/simulation/middleware.ts @@ -1,3 +1,4 @@ +import { BIP44Node } from '@metamask/key-tree'; import { logError } from '@metamask/snaps-utils'; import type { JsonRpcEngineEndCallback, @@ -7,10 +8,50 @@ import type { PendingJsonRpcResponse, } from 'json-rpc-engine'; +/* eslint-disable @typescript-eslint/naming-convention */ export const methodHandlers = { - // eslint-disable-next-line @typescript-eslint/naming-convention metamask_getProviderState: getProviderStateHandler, + eth_requestAccounts: getAccountsHandler, + eth_accounts: getAccountsHandler, }; +/* eslint-enable @typescript-eslint/naming-convention */ + +export type MiscMiddlewareHooks = { + getMnemonic: () => Promise; +}; + +/** + * A mock handler for account related methods that always returns the first address for the selected SRP. + * + * @param _request - Incoming JSON-RPC request, ignored for this specific handler. + * @param response - The outgoing JSON-RPC response, modified to return the result. + * @param _next - The json-rpc-engine middleware next handler. + * @param end - The json-rpc-engine middleware end handler. + * @param hooks - Any hooks required by this handler. + */ +async function getAccountsHandler( + _request: JsonRpcRequest, + response: PendingJsonRpcResponse, + _next: JsonRpcEngineNextCallback, + end: JsonRpcEngineEndCallback, + hooks: MiscMiddlewareHooks, +) { + const { getMnemonic } = hooks; + + const node = await BIP44Node.fromDerivationPath({ + derivationPath: [ + await getMnemonic(), + `bip32:44'`, + `bip32:60'`, + `bip32:0'`, + `bip32:0`, + `bip32:0`, + ], + }); + + response.result = [node.address]; + return end(); +} /** * A mock handler for metamask_getProviderState that always returns a specific hardcoded result. @@ -39,12 +80,14 @@ async function getProviderStateHandler( /** * Creates a middleware for handling misc RPC methods normally handled internally by the MM client. * + * NOTE: This middleware provides all `hooks` to all handlers and should therefore NOT be used outside of the simulator. + * + * @param hooks - Any hooks used by the middleware handlers. * @returns Nothing. */ -export function createMiscMethodMiddleware(): JsonRpcMiddleware< - unknown, - unknown -> { +export function createMiscMethodMiddleware( + hooks: MiscMiddlewareHooks, +): JsonRpcMiddleware { // This should probably use createAsyncMiddleware // eslint-disable-next-line @typescript-eslint/no-misused-promises return async function methodMiddleware(request, response, next, end) { @@ -53,7 +96,7 @@ export function createMiscMethodMiddleware(): JsonRpcMiddleware< if (handler) { try { // Implementations may or may not be async, so we must await them. - return await handler(request, response, next, end); + return await handler(request, response, next, end, hooks); } catch (error: any) { logError(error); return end(error); diff --git a/packages/snaps-simulator/src/features/simulation/sagas.ts b/packages/snaps-simulator/src/features/simulation/sagas.ts index 0acf1ca7dd..e68b9c4b24 100644 --- a/packages/snaps-simulator/src/features/simulation/sagas.ts +++ b/packages/snaps-simulator/src/features/simulation/sagas.ts @@ -80,16 +80,20 @@ export function* initSaga({ payload }: PayloadAction) { const srp: string = yield select(getSrp); + const sharedHooks = { + getMnemonic: async () => mnemonicPhraseToBytes(srp), + }; + const permissionSpecifications = { ...buildSnapEndowmentSpecifications(Object.keys(ExcludedSnapEndowments)), ...buildSnapRestrictedMethodSpecifications([], { + ...sharedHooks, // TODO: Add all the hooks required encrypt, decrypt, // TODO: Allow changing this? getLocale: async () => Promise.resolve('en'), getUnlockPromise: async () => Promise.resolve(true), - getMnemonic: async () => mnemonicPhraseToBytes(srp), showDialog: async (...args: Parameters) => await runSaga(showDialog, ...args).toPromise(), showNativeNotification: async ( @@ -137,7 +141,7 @@ export function* initSaga({ payload }: PayloadAction) { const engine = new JsonRpcEngine(); - engine.push(createMiscMethodMiddleware()); + engine.push(createMiscMethodMiddleware(sharedHooks)); engine.push( permissionController.createPermissionMiddleware({