Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for account RPC methods in snaps simulator #1710

Merged
merged 7 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions packages/examples/packages/ethereum-provider/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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'],
});
});
});
55 changes: 49 additions & 6 deletions packages/snaps-simulator/src/features/simulation/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BIP44Node } from '@metamask/key-tree';
import { logError } from '@metamask/snaps-utils';
import type {
JsonRpcEngineEndCallback,
Expand All @@ -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<Uint8Array>;
};

/**
* 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<unknown>,
response: PendingJsonRpcResponse<unknown>,
_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.
Expand Down Expand Up @@ -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<unknown, unknown> {
// This should probably use createAsyncMiddleware
// eslint-disable-next-line @typescript-eslint/no-misused-promises
return async function methodMiddleware(request, response, next, end) {
Expand All @@ -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);
Expand Down
8 changes: 6 additions & 2 deletions packages/snaps-simulator/src/features/simulation/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,20 @@

const srp: string = yield select(getSrp);

const sharedHooks = {
getMnemonic: async () => mnemonicPhraseToBytes(srp),

Check warning on line 84 in packages/snaps-simulator/src/features/simulation/sagas.ts

View check run for this annotation

Codecov / codecov/patch

packages/snaps-simulator/src/features/simulation/sagas.ts#L84

Added line #L84 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

Can this be covered?

Copy link
Member Author

Choose a reason for hiding this comment

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

None of the hooks are currently covered because we don't have tests that run them directly. IMO out of scope for this PR and not exactly sure how we test to cover them

};

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<typeof showDialog>) =>
await runSaga(showDialog, ...args).toPromise(),
showNativeNotification: async (
Expand Down Expand Up @@ -137,7 +141,7 @@

const engine = new JsonRpcEngine();

engine.push(createMiscMethodMiddleware());
engine.push(createMiscMethodMiddleware(sharedHooks));

engine.push(
permissionController.createPermissionMiddleware({
Expand Down